Fix pager rendering slowness.

This commit is contained in:
Greyson Parrelli
2026-08-14 23:00:27 -04:00
parent e4b9fac3c9
commit 043b994a15
+64 -3
View File
@@ -38,6 +38,11 @@ watch: c_uint = 0,
/// True once the child process has exited and the PTY hung up.
exited: bool = false,
/// Cell size in pixels, as last set by a resize. Kept because the XTWINOPS
/// size queries report it and the answer has to come from somewhere; the
/// widget owns the real geometry and hands it down here.
cell_px: struct { w: u32 = 8, h: u32 = 16 } = .{},
/// Latest state reported by the child. Kept so a repeated report of the
/// state we are already in doesn't wake the UI for nothing.
status: Status = .idle,
@@ -139,13 +144,23 @@ pub fn create(
errdefer self.stream.deinit();
// Wire up the side effects we care about. `readonly` handles terminal
// state but silently drops anything that needs to talk back to the
// child; we need the writes so that queries (cursor position, device
// attributes, in-band resize) get answers.
// state but silently drops anything that needs to talk back to the child.
//
// Every query below has to be answered, not merely permitted to write.
// `write_pty` is only the pipe; a query whose own effect is null is
// dropped before it ever reaches that pipe. And an unanswered query is
// not a missing feature, it is a stall: the standard way to test for a
// capability is to send the query followed by a device attributes
// request, and take the DA1 reply arriving alone as "unsupported".
// A terminal that answers neither leaves the program waiting out its
// own timeout, once per run, for every capability it probes.
var effects = vt.TerminalStream.Handler.Effects.readonly;
effects.write_pty = &effectWritePty;
effects.title_changed = &effectTitleChanged;
effects.progress_report = &effectProgressReport;
effects.device_attributes = &effectDeviceAttributes;
effects.color_scheme = &effectColorScheme;
effects.size = &effectSize;
self.stream.handler.effects = effects;
const shell = try defaultShell(alloc);
@@ -268,6 +283,48 @@ fn effectWritePty(handler: *vt.TerminalStream.Handler, data: [:0]const u8) void
}
/// Effect callback: OSC 0/2 changed the window title.
/// The reply type an effect has to return, recovered from the effect's own
/// signature. libghostty-vt exports the device attributes *request* enum by
/// name but not the reply struct, and naming it this way can't drift from
/// what the callback is actually required to return.
fn EffectReply(comptime name: []const u8) type {
const Opt = @FieldType(vt.TerminalStream.Handler.Effects, name);
const Fn = @typeInfo(@typeInfo(Opt).optional.child).pointer.child;
return @typeInfo(Fn).@"fn".return_type.?;
}
/// Effect callback: the child asked what kind of terminal this is (CSI c and
/// friends). The defaults describe a VT220 that does ANSI colour, which is
/// what this actually is — the answer matters less than there being one.
fn effectDeviceAttributes(
_: *vt.TerminalStream.Handler,
) EffectReply("device_attributes") {
return .{};
}
/// Effect callback: the child asked whether it is drawing on a light or dark
/// background (CSI ? 996 n), so it can pick readable colours. Answered from
/// the scheme the grid is actually painted in.
fn effectColorScheme(
_: *vt.TerminalStream.Handler,
) ?vt.device_status.ColorScheme {
return switch (theme.currentScheme()) {
.light => .light,
.dark => .dark,
};
}
/// Effect callback: the child asked for the terminal geometry (CSI 14/16/18 t).
fn effectSize(handler: *vt.TerminalStream.Handler) ?vt.size_report.Size {
const self = fromHandler(handler);
return .{
.rows = self.term.rows,
.columns = self.term.cols,
.cell_width = self.cell_px.w,
.cell_height = self.cell_px.h,
};
}
fn effectTitleChanged(handler: *vt.TerminalStream.Handler) void {
const self = fromHandler(handler);
self.on_title(self.ctx, self.term.title.items);
@@ -320,6 +377,10 @@ pub fn write(self: *Session, bytes: []const u8) void {
/// Resize the terminal grid and tell the child about it.
pub fn resize(self: *Session, cols: u16, rows: u16, cell_w: u32, cell_h: u32) !void {
// Recorded before the early return: the grid can keep its dimensions
// across a font change that moves every pixel measurement.
self.cell_px = .{ .w = cell_w, .h = cell_h };
if (cols == self.term.cols and rows == self.term.rows) return;
try self.stream.handler.resize(.{