From 402dd83770e40c0b7b301a6a6f4736bed718a062 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Wed, 19 Aug 2026 16:08:00 +0000 Subject: [PATCH] resize: preserve screen state on allocation failure Amp-Thread-ID: https://ampcode.com/threads/T-01a016b4-7f3b-723c-9a8b-daa5588744b0 Co-authored-by: Tim Culverhouse --- src/Vaxis.zig | 36 +++++++++++++++++++++++++++---- src/widgets/terminal/Screen.zig | 23 ++++++++++++++++++++ src/widgets/terminal/Terminal.zig | 18 ++++++++++------ 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/Vaxis.zig b/src/Vaxis.zig index 47aa49b..8bc67fc 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -199,14 +199,22 @@ pub fn resize( winsize: Winsize, ) !void { log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); + const replacements = blk: { + var screen = try Screen.init(alloc, winsize); + errdefer screen.deinit(alloc); + screen.width_method = self.caps.unicode; + var screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows); + errdefer screen_last.deinit(alloc); + break :blk .{ .screen = screen, .screen_last = screen_last }; + }; + self.screen.deinit(alloc); - self.screen = try Screen.init(alloc, winsize); - self.screen.width_method = self.caps.unicode; + self.screen_last.deinit(alloc); + self.screen = replacements.screen; + self.screen_last = replacements.screen_last; // try self.screen.int(alloc, winsize.cols, winsize.rows); // we only init our current screen. This has the effect of redrawing // every cell - self.screen_last.deinit(alloc); - self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows); if (self.state.alt_screen) try tty.writeAll(ctlseqs.home) else { @@ -1530,3 +1538,23 @@ test "render: no output when no changes" { defer std.testing.allocator.free(output); try std.testing.expectEqual(@as(usize, 0), output.len); } + +fn testResizeAllocationFailures(allocator: std.mem.Allocator) !void { + var env_map = try std.testing.environ.createMap(allocator); + defer env_map.deinit(); + var vx = try Vaxis.init(std.testing.io, allocator, &env_map, .{}); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); + defer vx.deinit(allocator, &writer.writer); + + try vx.resize(allocator, &writer.writer, .{ .rows = 2, .cols = 2, .x_pixel = 0, .y_pixel = 0 }); + try vx.resize(allocator, &writer.writer, .{ .rows = 3, .cols = 3, .x_pixel = 0, .y_pixel = 0 }); +} + +test "resize preserves valid state on allocation failure" { + try std.testing.checkAllAllocationFailures( + std.testing.allocator, + testResizeAllocationFailures, + .{}, + ); +} diff --git a/src/widgets/terminal/Screen.zig b/src/widgets/terminal/Screen.zig index 8fa33d2..e7f126a 100644 --- a/src/widgets/terminal/Screen.zig +++ b/src/widgets/terminal/Screen.zig @@ -108,10 +108,20 @@ pub fn init(alloc: std.mem.Allocator, w: u16, h: u16) !Screen { .width = w, .height = h, }; + var initialized: usize = 0; + errdefer { + for (screen.buf[0..initialized]) |*cell| { + cell.char.deinit(alloc); + cell.uri.deinit(alloc); + cell.uri_id.deinit(alloc); + } + alloc.free(screen.buf); + } for (screen.buf, 0..) |_, i| { screen.buf[i] = .{ .char = try .initCapacity(alloc, 1), }; + initialized += 1; try screen.buf[i].char.append(alloc, ' '); } return screen; @@ -512,3 +522,16 @@ pub fn scrollDown(self: *Screen, n: usize) !void { self.cursor.row = self.scrolling_region.top; try self.insertLine(n); } + +fn testInitAllocationFailures(allocator: std.mem.Allocator) !void { + var screen = try Screen.init(allocator, 3, 2); + defer screen.deinit(allocator); +} + +test "init cleans up allocation failures" { + try std.testing.checkAllAllocationFailures( + std.testing.allocator, + testInitAllocationFailures, + .{}, + ); +} diff --git a/src/widgets/terminal/Terminal.zig b/src/widgets/terminal/Terminal.zig index d495ba4..696d322 100644 --- a/src/widgets/terminal/Terminal.zig +++ b/src/widgets/terminal/Terminal.zig @@ -211,15 +211,21 @@ pub fn resize(self: *Terminal, ws: Winsize) !void { try self.back_mutex.lock(self.io); defer self.back_mutex.unlock(self.io); - self.front_screen.deinit(self.allocator); - self.front_screen = try Screen.init(self.allocator, ws.cols, ws.rows); + var front_screen = try Screen.init(self.allocator, ws.cols, ws.rows); + errdefer front_screen.deinit(self.allocator); + var back_screen_pri = try Screen.init(self.allocator, ws.cols, ws.rows + self.scrollback_size); + errdefer back_screen_pri.deinit(self.allocator); + var back_screen_alt = try Screen.init(self.allocator, ws.cols, ws.rows); + errdefer back_screen_alt.deinit(self.allocator); + + try self.pty.setSize(ws); + self.front_screen.deinit(self.allocator); self.back_screen_pri.deinit(self.allocator); self.back_screen_alt.deinit(self.allocator); - self.back_screen_pri = try Screen.init(self.allocator, ws.cols, ws.rows + self.scrollback_size); - self.back_screen_alt = try Screen.init(self.allocator, ws.cols, ws.rows); - - try self.pty.setSize(ws); + self.front_screen = front_screen; + self.back_screen_pri = back_screen_pri; + self.back_screen_alt = back_screen_alt; } pub fn draw(self: *Terminal, allocator: std.mem.Allocator, win: vaxis.Window) !void { -- 2.51.2