Register parsing now validates the full register request shape before
constructing the parsed command. Inputs that only contain the verb
separator, such as `r`, `r;cp=e0a0`, or `r;foo`, now fail with
InvalidFormat instead of reaching Register invariants guarded by asserts.
Valid empty-payload requests still parse when they include the payload
separator, allowing execution to report malformed_payload through the
normal protocol response path.
Glyph clear execution previously treated an unparsable cp option the same
as an omitted cp option. That made inputs such as c;cp=zz behave like a
bare clear request and remove every glossary registration.
Track clear option presence separately from successful decoding. A
present but malformed cp now returns a malformed_payload clear failure
without mutating the glossary, while an omitted cp still clears all
registrations.
Adds a PageList regression test exercising the underflow path fixed in
7fa6fffbc, and a libghostty-vt C API test mirroring the original repro
through ghostty_terminal_resize.
PageList.resize takes the .lt branch when columns shrink, which calls
resizeWithoutReflow (mutating self.rows to the new smaller value) and
then resizeCols with the original opts.cursor.y. When both axes shrink
in one call and the cursor sits at or past the new bottom row, the
expression `self.rows - c.y - 1` underflows and panics in safety builds.
Use saturating subtraction; "remaining rows below cursor" is 0 once the
cursor sits at or past the new bottom.
`setSelection` captured the previous selection, then called
`Screen.select` (which deinits the previous selection's tracked pins),
then compared the new selection against the now-freed previous pin via
`sel.eql(prev)`. That read freed pin memory (use-after-free).
The comparison was a copy-on-select optimization ("only re-copy if the
selection changed"). Remove it rather than repair it because:
- It never fired correctly. It compared against freed memory, so the
shipped behavior was already "always copy".
- It can't be repaired by copying `prev`'s pin before `Screen.select`.
That fixes the use-after-free but not the logic: the call sites (e.g.
mouse drag release) pass a selection equal to the one already set, so a
working `eql` skip would suppress the very copy those sites exist to
perform. A correct optimization would have to compare against the
last-copied selection (before the mouse event mutated the live one),
which would require extra state.
- It isn't worth tracking that additional state. The copy runs once per
selection gesture (mouse up, double-click), which isn't in a hot path,
so skipping a redundant re-copy only saves a single clipboard write.
Removing the skip eliminates the use-after-free and keeps the behavior
consistent with what we've already been doing.
---
_AI Disclosure_: Claude Opus 4.8 found this in a review while I was
working on adjacent code.
This adds the core parse/encode for the still in-development and experimental
terminal glyph protocol: https://github.com/raphamorim/rio/pull/1542
Up to version 1.9.
The only cross-cutting change necessary was changing the APC
identification logic which previously only looked at a single byte to
support multi-byte identifiers since the glyph protocol uses `25a1`.
Add a render-state row-cells getter that encodes the current cell's
full grapheme cluster directly as UTF-8 into a caller-provided
GhosttyBuffer. The getter writes the base codepoint first, followed by
any extra grapheme codepoints, and follows the existing buffer-writer
convention where len is bytes written on success or required capacity
on GHOSTTY_OUT_OF_SPACE.
Previously C consumers could query grapheme codepoints, but bindings
that needed UTF-8 text had to reconstruct and encode the cluster
themselves. That duplicated terminal internals in downstream bindings
and made users pay for awkward cross-language struct handling. By
owning the UTF-8/grapheme behavior in libghostty, bindings can use one
stable C API and optionally wrap it with small binding-local helpers.
Add a render row-cells data key for querying whether the current cell has
explicit styling. This lets consumers avoid fetching a raw cell or full style
snapshot when all they need is the cell's HasStyling bit.
The new key is appended to the existing enum for ABI safety and is served by
the existing row-cells getter path. Existing data keys and function exports are
unchanged.
Expose whether the terminal viewport is currently pinned to the active
area through the libghostty-vt terminal data API. Previously embedders
could only infer this from scrollbar geometry, which was indirect and
could require the more expensive scrollbar calculation.
The new GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE value returns the exact
PageList viewport state as a bool. The scroll viewport test now verifies
the value while moving between the active area and scrollback.
Selection gestures now treat releases with invalidated anchors as dragged,
so a press that crosses screen boundaries cannot also activate links or
prompt clicks on release. Cell drags that create a same-cell selection also
mark the gesture as dragged, which keeps click-only actions from firing
after a threshold-crossing drag.
Autoscroll now resolves the drag pin after moving the viewport instead of
reusing the pin from before the scroll. This keeps the selection aligned
with the row currently under the pointer. The inspector also validates the
tracked click pin before displaying it so stale pins from inactive screens
are ignored.
Render-state rows already expose their selected range, but
cell-oriented C API consumers had to fetch that row range separately
and duplicate the containment check while rendering.
Add a SELECTED row-cells data kind that carries the row selection into
the row-cells wrapper and returns whether the current cell column is in
that inclusive range. The field remains separate from cell colors and
style so selection stays an explicit render overlay policy.
For performance reasons, the span-based row getter is recommended still
but this is a convenient thing to do for cell-oriented folks.
Tracked grid references previously held a raw terminal wrapper pointer
and were required to be freed before the terminal. If callers kept one
past terminal destruction, later tracked-ref calls could dereference
freed terminal or page-list memory before detecting that the reference
was no longer meaningful.
Track live C tracked-grid-ref handles from the terminal wrapper and
detach them before tearing down terminal storage. Detached refs now
report no value through the tracked-ref APIs and can still be freed by
the caller. Update the C API docs to describe this lifetime behavior and
add a regression test for using a tracked ref after terminal free.
This introduces some overhead but tracked pins shouldn't be numerous and
this dramatically improves safety.
No API changes due to this (just more safety).
Tracked grid references previously held a raw terminal wrapper pointer and
were required to be freed before the terminal. If callers kept one past
terminal destruction, later tracked-ref calls could dereference freed
terminal or page-list memory before detecting that the reference was no
longer meaningful.
Track live C tracked-grid-ref handles from the terminal wrapper and detach
them before tearing down terminal storage. Detached refs now report no
value through the tracked-ref APIs and can still be freed by the caller.
Update the C API docs to describe this lifetime behavior and add a
regression test for using a tracked ref after terminal free.
This introduces some overhead but tracked pins shouldn't be numerous
and this dramatically improves safety.
Expose a C API for checking whether a GhosttyPoint is inside a
GhosttySelection. The new terminal helper validates the selection snapshot
against the active screen, resolves the point to a grid pin, and delegates
to the internal Selection.contains implementation so C consumers get the
same linear and rectangular selection semantics as Ghostty.
Wire the symbol through the C API exports and public headers, and add a
focused test covering linear containment and rectangular selection behavior.
Expose selection endpoint ordering through the libghostty-vt C API so
embedders can safely normalize selections whose start and end refs may be
reversed. The new APIs report the current order and return a fresh
untracked selection with forward or reverse bounds.
Selection.Order now uses lib.Enum, matching the existing adjustment enum
pattern and keeping the C ABI enum generated from the same Zig source of
truth. The new functions are wired through the C API re-export and lib-vt
export paths, with coverage for mirrored rectangular selection ordering.