Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/manual-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ WezTerm (and Ghostty when available) before tagging a release.
stuck at its old position overlapping buffer text (issue #18).
- [ ] Scrolling the image fully out of view then back in re-displays it
without a visible retransmission delay (cache hit).
- [ ] `show()` the same PNG path twice at two different buffer lines
without `clear()`-ing the first in between (issue #10, multi-location
fan-out). Both copies must render correctly and simultaneously β€” not
just the second one, and not the first one moved/disappeared. Then
`clear()` only the first handle: the second must remain visible,
unaffected. Finally `clear()` the second handle too and confirm no
stray pixels remain from either.
- [ ] Scrolling so the image is cut off at the top or bottom of the window
shows a **cropped** slice of the image (the still-visible portion,
correctly sized to the remaining cell span) instead of a blank gap or
Expand Down
56 changes: 39 additions & 17 deletions docs/spec/kitty-graphics.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Every command is an APC (Application Program Command) escape sequence:
| `i` | image id | one of blit's reserved range, see below |
| `q` | quiet | `2` (suppress all responses) always, see "Response handling" below |
| `m` | more chunks | `1` (more chunks follow) / `0` (last chunk) |
| `p` | placement id | always `1` (blit's single fixed placement id β€” see "Placement" below) |
| `p` | placement id | a per-handle id, distinct across every concurrently-live placement β€” see "Placement" below |
| `c`, `r` | placement columns/rows | shrink to the visible cell span when a placement is partially clipped, see "Source-rectangle cropping" below |
| `x`, `y` | source rectangle pixel offset (left/top) into the transmitted image | present only when a placement is partially clipped; see "Source-rectangle cropping" below |
| `w`, `h` | source rectangle pixel size | present only when a placement is partially clipped; see "Source-rectangle cropping" below |
Expand All @@ -53,23 +53,34 @@ Every command is an APC (Application Program Command) escape sequence:

## Placement

- `a=p,i=<id>,p=1` redisplays an already-transmitted image without resending
pixel data. This is how blit satisfies the performance rule that
scroll/resize redraws must reuse the existing id rather than
- `a=p,i=<id>,p=<placement_id>` redisplays an already-transmitted image
without resending pixel data. This is how blit satisfies the performance
rule that scroll/resize redraws must reuse the existing id rather than
re-transmitting.
- `p=1` (blit's fixed placement id, `terminal.PLACEMENT_ID`) is **always**
sent, on every placement command β€” both the initial `a=T` transmit+display
and every later `a=p` reposition. If `p=` is omitted, the terminal creates
a brand-new placement on every call instead of moving the existing one;
since blit repositions on every debounced `WinScrolled`/`WinResized`
redraw, this silently accumulates stacked "ghost" placements at each prior
screen position β€” visible as partial/duplicated image fragments while
scrolling, until a `a=d,d=i` delete (see below) clears all of them at
once. Reusing the same `i=` **and** `p=` pair on every call makes each
`a=p` update that one placement in place instead. blit never needs more
than one placement id per image id: `renderer.lua`'s cache only ever marks
a given image id "active" for a single handle at a time, so a constant
`p=1` can never collide with a second live placement of the same id.
- `p=` (a caller-supplied placement id, `blit.terminal.PlacementOpts.placement_id`
in `terminal.lua`) is **always** sent, on every placement command β€” both
the initial `a=T` transmit+display and every later `a=p` reposition. If
`p=` is omitted, the terminal creates a brand-new placement on every call
instead of moving the existing one; since blit repositions on every
debounced `WinScrolled`/`WinResized` redraw, this silently accumulates
stacked "ghost" placements at each prior screen position β€” visible as
partial/duplicated image fragments while scrolling, until a `a=d,d=i`
delete (see below) clears all of them at once. Reusing the same `i=`
**and** `p=` pair on every call makes each `a=p` update that one placement
in place instead.
- **One image id can have several concurrent placements (issue #10,
"Multi-location placement fan-out").** Earlier versions of blit sent a
single fixed `p=1` on every call, relying on `renderer.lua`'s cache never
marking a given image id "active" for more than one handle at a time. That
constraint is gone: `renderer.lua` now allocates a distinct, never-reused
placement id per handle (`alloc_placement_id()`, an ever-incrementing
session-lifetime counter β€” see `docs/spec/renderer-placement.md`'s
"Transmission cache" section), so a second `show()` of the same
`(path, mtime)` while the first is still live reuses the SAME image id
under a DIFFERENT placement id β€” a second, independent placement β€” instead
of transmitting a redundant copy under a new image id. Each placement is
then addressed, repositioned, and torn down independently by its own
`(id, placement_id)` pair.
- Optional placement keys, in the fixed order blit emits them: `p=` (always
present when placing), `x=`, `y=`, `w=`, `h=` (source rectangle, only when
cropped β€” see "Source-rectangle cropping" below), `c=`, `r=` (target cell
Expand Down Expand Up @@ -144,6 +155,17 @@ clip-amount math (`compute_clip`) and the pixel-space conversion

- `a=d,d=i,i=<id>` deletes the visible placement(s) for one image id blit
owns; `d=I` additionally frees the terminal's stored pixel data for that id.
- `p=<placement_id>`, when supplied alongside `d=i`, restricts the delete to
that ONE placement of the id rather than every placement blit has made for
it β€” required now that one id can have several concurrent placements (see
"Placement" above). `renderer.lua` always includes it except for the
whole-id teardown case: freeing a handle's placement while the id might
still be shared by a sibling handle (`terminal.build_delete(id, {
placement_id = ... })`), vs. freeing the id's stored data entirely once no
handle references it anymore (`terminal.build_delete(id, { free_data =
true })`, no `p=`, since every placement is being torn down together at
that point anyway) β€” see `docs/spec/renderer-placement.md`'s Lifecycle
section for the full decision.
- blit **never** emits `d=a` (delete every image on the terminal, including
ones placed by other plugins like image.nvim/snacks.image). Every deletion
path in `terminal.lua` is scoped to a single caller-supplied id.
Expand Down
Loading
Loading