Move mouse event encoding logic from Surface.zig into a new
input/mouse_encode.zig file.
The new file encapsulates event filtering (shouldReport),
button code computation, viewport bounds checking, motion
deduplication, and all five wire formats (X10, UTF-8, SGR,
urxvt, SGR-pixels). This makes the encoding independently
testable and adds unit tests covering each format and edge
case.
Additionally, Surface `mouseReport` can no longer fail, since the only
failure mode is no buffer space which should be impossible. Updated
the signature to remove the error set.
Expose the key encoder Options.fromTerminal function to the C API as
ghostty_key_encoder_setopt_from_terminal. This lets C callers sync all
terminal-derived encoding options (cursor key application mode, keypad
mode, alt escape prefix, modifyOtherKeys, and Kitty flags) in a single
call instead of setting each option individually.
Expose the key encoder Options.fromTerminal function to the C API as
ghostty_key_encoder_setopt_from_terminal. This lets C callers sync all
terminal-derived encoding options (cursor key application mode, keypad
mode, alt escape prefix, modifyOtherKeys, and Kitty flags) in a single
call instead of setting each option individually.
Change `window-padding-balance` from `bool` to an enum with three
values:
- `false` - no balancing (default, unchanged)
- `true` - balance with vshift that caps top padding and shifts excess
to bottom (existing behavior, unchanged)
- `equal` - balance whitespace equally on all four sides
This gives users who prefer truly equal padding a way to opt in without
changing the default behavior.
Change `window-padding-balance` from `bool` to an enum with three
values:
- `false` - no balancing (default, unchanged)
- `true` - balance with vshift that caps top padding and shifts excess
to bottom (existing behavior, unchanged)
- `equal` - balance whitespace equally on all four sides
This gives users who prefer truly equal padding a way to opt in without
changing the default behavior.
This adds an initial C API for terminals and formatting. There is a new
example that shows how to use this.
With these APIs in place, users of the C API can now create a terminal,
pass raw VT streams to it, and dump the terminal viewport to various
formats. As noted in the docs, **the formatter API is not a rendering
API**, it isn't high performance enough for that. But it's a simpler API
to implement than the render state API so I started with that.
Both APIs are purposely fairly minimal, we're just setting the stage for
future functionality.
## Example
```c
#include <ghostty/vt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
GhosttyTerminal term;
GhosttyTerminalOptions opts = { .cols = 80, .rows = 24, .max_scrollback = 0 };
ghostty_terminal_new(NULL, &term, opts);
const char *input = "Hello, \033[1mBold\033[0m World!\r\nLine 2\r\n";
ghostty_terminal_vt_write(term, (const uint8_t *)input, strlen(input));
GhosttyFormatterTerminalOptions fmt = GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
fmt.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
fmt.trim = true;
GhosttyFormatter fmtr;
ghostty_formatter_terminal_new(NULL, &fmtr, term, fmt);
uint8_t *buf;
size_t len;
ghostty_formatter_format_alloc(fmtr, NULL, &buf, &len);
fwrite(buf, 1, len, stdout);
free(buf);
ghostty_formatter_free(fmtr);
ghostty_terminal_free(term);
}
```
## New APIs
| Function | Description |
|----------|-------------|
| `ghostty_terminal_new` | Create a new terminal instance |
| `ghostty_terminal_free` | Free a terminal instance |
| `ghostty_terminal_reset` | Full reset of the terminal (RIS) |
| `ghostty_terminal_resize` | Resize the terminal to given dimensions |
| `ghostty_terminal_vt_write` | Write VT-encoded data to the terminal |
| `ghostty_terminal_scroll_viewport` | Scroll the terminal viewport |
| `ghostty_formatter_terminal_new` | Create a formatter for a terminal's
active screen |
| `ghostty_formatter_format_buf` | Format into a caller-provided buffer
|
| `ghostty_formatter_format_alloc` | Format into an allocated buffer |
| `ghostty_formatter_free` | Free a formatter instance |
## Future
- Obviously need to expose a lot more from the terminal:
* Read current set modes
* Read cursor information
* Read screen information
* etc...
- Need an optional callback system so that `vt_write` can invoke
callbacks for side effect sequences like clipboards, title setting,
responses, etc.
- `terminal.RenderState` C API so that people can build high performance
renderers on top of libghostty-vt
And so on...
The Discarding writer count field is u64, but several call sites
pass it where a usize is expected. On wasm32-freestanding, usize is
32-bit, so this caused compilation errors.
Use std.math.cast instead of a bare @intCast so that overflow is
handled gracefully, returning WriteFailed rather than triggering
safety-checked undefined behavior at runtime.
The Discarding writer count field is u64, but appendNTimes expects
usize which is u32 on 32-bit targets like arm-linux-androideabi.
Use std.math.cast instead of @intCast to safely handle the
conversion, returning WriteFailed on overflow rather than risking
undefined behavior.
Add an example showing how to use the ghostty-vt terminal and
formatter APIs from C. The example creates a terminal, writes
VT-encoded content with cursor movement and styling sequences,
then formats the screen contents as plain text using the formatter
API.
Rename the existing format function to format_buf to clarify that it
writes into a caller-provided buffer. Add a new format_alloc variant
that allocates the output buffer internally using the provided
allocator (or the default if NULL). The caller receives the allocated
pointer and length and is responsible for freeing it.
This is useful for consumers that do not know the required buffer size
ahead of time and want to avoid the two-pass query-then-format pattern
needed with format_buf.
Add a size field as the first member of formatter option structs
(TerminalOptions, TerminalOptions.Extra, ScreenOptions.Extra) for ABI
compatibility. This allows adding new fields without breaking callers
compiled against older versions of the struct.
Introduce include/ghostty/vt/types.h as the foundational header
containing GhosttyResult and the GHOSTTY_INIT_SIZED macro for
zero-initializing sized structs. Remove the separate result.h header,
moving its contents into types.h.
The terminal.Stream next/nextSlice functions can now no longer fail. All
prior failure modes were fully isolated in the handler `vt` callbacks.
As such, vt callbacks are now required to not return an error and handle
their own errors somehow.
Allowing streams to be fallible before was an incorrect design. It
caused problematic scenarios like in `nextSlice` early terminating
processing due to handler errors. This should not be possible.
There is no safe way to bubble up vt errors through the stream because
if nextSlice is called and multiple errors are returned, we can't
coalesce them. We could modify that to return a partial result but its
just more work for stream that is unnecessary. The handler can do all of
this.
This work was discovered due to cleanups to prepare for more C APIs.
Less errors make C APIs easier to implement! And, it helps clean up our
Zig, too.
The terminal.Stream next/nextSlice functions can now no longer fail.
All prior failure modes were fully isolated in the handler `vt`
callbacks. As such, vt callbacks are now required to not return an error
and handle their own errors somehow.
Allowing streams to be fallible before was an incorrect design. It
caused problematic scenarios like in `nextSlice` early terminating
processing due to handler errors. This should not be possible.
There is no safe way to bubble up vt errors through the stream because
if nextSlice is called and multiple errors are returned, we can't
coalesce them. We could modify that to return a partial result but its
just more work for stream that is unnecessary. The handler can do all of
this.
This work was discovered due to cleanups to prepare for more C APIs.
Less errors make C APIs easier to implement! And, it helps clean up our
Zig, too.
Added test case for cascading **without moving previous window**, #11161
will follow up for more accurate cascading after this.
Fixed window cascading after last pr, now we should perform cascading
**after** showing the window.