mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-22 19:56:08 +03:00
terminal: handle resizing into increased implicit max size
This commit is contained in:
@ -107,7 +107,12 @@ page_size: usize,
|
|||||||
/// Maximum size of the page allocation in bytes. This only includes pages
|
/// Maximum size of the page allocation in bytes. This only includes pages
|
||||||
/// that are used ONLY for scrollback. If the active area is still partially
|
/// that are used ONLY for scrollback. If the active area is still partially
|
||||||
/// in a page that also includes scrollback, then that page is not included.
|
/// in a page that also includes scrollback, then that page is not included.
|
||||||
max_size: usize,
|
explicit_max_size: usize,
|
||||||
|
|
||||||
|
/// This is the minimum max size that we will respect due to the rows/cols
|
||||||
|
/// of the PageList. We must always be able to fit at least the active area
|
||||||
|
/// and at least two pages for our algorithms.
|
||||||
|
min_max_size: usize,
|
||||||
|
|
||||||
/// The list of tracked pins. These are kept up to date automatically.
|
/// The list of tracked pins. These are kept up to date automatically.
|
||||||
tracked_pins: PinSet,
|
tracked_pins: PinSet,
|
||||||
@ -149,6 +154,29 @@ pub const Viewport = union(enum) {
|
|||||||
pin,
|
pin,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Returns the minimum valid "max size" for a given number of rows and cols
|
||||||
|
/// such that we can fit the active area AND at least two pages. Note we
|
||||||
|
/// need the two pages for algorithms to work properly (such as grow) but
|
||||||
|
/// we don't need to fit double the active area.
|
||||||
|
fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) !usize {
|
||||||
|
// Get our capacity to fit our rows. If the cols are too big, it may
|
||||||
|
// force less rows than we want meaning we need more than one page to
|
||||||
|
// represent a viewport.
|
||||||
|
const cap = try std_capacity.adjust(.{ .cols = cols });
|
||||||
|
|
||||||
|
// Calculate the number of standard sized pages we need to represent
|
||||||
|
// an active area. We always need at least two pages so if we can fit
|
||||||
|
// all our rows in one cap then we say 2, otherwise we do the math.
|
||||||
|
const pages = if (cap.rows >= rows) 2 else try std.math.divCeil(
|
||||||
|
usize,
|
||||||
|
rows,
|
||||||
|
cap.rows,
|
||||||
|
);
|
||||||
|
assert(pages >= 2);
|
||||||
|
|
||||||
|
return std_size * pages;
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the page. The top of the first page in the list is always the
|
/// Initialize the page. The top of the first page in the list is always the
|
||||||
/// top of the active area of the screen (important knowledge for quickly
|
/// top of the active area of the screen (important knowledge for quickly
|
||||||
/// setting up cursors in Screen).
|
/// setting up cursors in Screen).
|
||||||
@ -201,14 +229,8 @@ pub fn init(
|
|||||||
page_list.prepend(page);
|
page_list.prepend(page);
|
||||||
const page_size = page_buf.len;
|
const page_size = page_buf.len;
|
||||||
|
|
||||||
// The max size has to be adjusted to at least fit one viewport.
|
// Get our minimum max size, see doc comments for more details.
|
||||||
// We use item_size*2 because the active area can always span two
|
const min_max_size = try minMaxSize(cols, rows);
|
||||||
// pages as we scroll, otherwise we'd have to constantly copy in the
|
|
||||||
// small limit case.
|
|
||||||
const max_size_actual = @max(
|
|
||||||
max_size orelse std.math.maxInt(usize),
|
|
||||||
PagePool.item_size * 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
// We always track our viewport pin to ensure this is never an allocation
|
// We always track our viewport pin to ensure this is never an allocation
|
||||||
const viewport_pin = try pool.pins.create();
|
const viewport_pin = try pool.pins.create();
|
||||||
@ -223,7 +245,8 @@ pub fn init(
|
|||||||
.pool_owned = true,
|
.pool_owned = true,
|
||||||
.pages = page_list,
|
.pages = page_list,
|
||||||
.page_size = page_size,
|
.page_size = page_size,
|
||||||
.max_size = max_size_actual,
|
.explicit_max_size = max_size orelse std.math.maxInt(usize),
|
||||||
|
.min_max_size = min_max_size,
|
||||||
.tracked_pins = tracked_pins,
|
.tracked_pins = tracked_pins,
|
||||||
.viewport = .{ .active = {} },
|
.viewport = .{ .active = {} },
|
||||||
.viewport_pin = viewport_pin,
|
.viewport_pin = viewport_pin,
|
||||||
@ -450,7 +473,8 @@ pub fn clone(
|
|||||||
},
|
},
|
||||||
.pages = page_list,
|
.pages = page_list,
|
||||||
.page_size = page_size,
|
.page_size = page_size,
|
||||||
.max_size = self.max_size,
|
.explicit_max_size = self.explicit_max_size,
|
||||||
|
.min_max_size = self.min_max_size,
|
||||||
.cols = self.cols,
|
.cols = self.cols,
|
||||||
.rows = self.rows,
|
.rows = self.rows,
|
||||||
.tracked_pins = tracked_pins,
|
.tracked_pins = tracked_pins,
|
||||||
@ -502,6 +526,16 @@ pub const Resize = struct {
|
|||||||
pub fn resize(self: *PageList, opts: Resize) !void {
|
pub fn resize(self: *PageList, opts: Resize) !void {
|
||||||
if (!opts.reflow) return try self.resizeWithoutReflow(opts);
|
if (!opts.reflow) return try self.resizeWithoutReflow(opts);
|
||||||
|
|
||||||
|
// Recalculate our minimum max size. This allows grow to work properly
|
||||||
|
// when increasing beyond our initial minimum max size or explicit max
|
||||||
|
// size to fit the active area.
|
||||||
|
const old_min_max_size = self.min_max_size;
|
||||||
|
self.min_max_size = try minMaxSize(
|
||||||
|
opts.cols orelse self.cols,
|
||||||
|
opts.rows orelse self.rows,
|
||||||
|
);
|
||||||
|
errdefer self.min_max_size = old_min_max_size;
|
||||||
|
|
||||||
// On reflow, the main thing that causes reflow is column changes. If
|
// On reflow, the main thing that causes reflow is column changes. If
|
||||||
// only rows change, reflow is impossible. So we change our behavior based
|
// only rows change, reflow is impossible. So we change our behavior based
|
||||||
// on the change of columns.
|
// on the change of columns.
|
||||||
@ -1148,6 +1182,15 @@ fn reflowUpdateCursor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
||||||
|
// We only set the new min_max_size if we're not reflowing. If we are
|
||||||
|
// reflowing, then resize handles this for us.
|
||||||
|
const old_min_max_size = self.min_max_size;
|
||||||
|
self.min_max_size = if (!opts.reflow) try minMaxSize(
|
||||||
|
opts.cols orelse self.cols,
|
||||||
|
opts.rows orelse self.rows,
|
||||||
|
) else old_min_max_size;
|
||||||
|
errdefer self.min_max_size = old_min_max_size;
|
||||||
|
|
||||||
if (opts.rows) |rows| {
|
if (opts.rows) |rows| {
|
||||||
switch (std.math.order(rows, self.rows)) {
|
switch (std.math.order(rows, self.rows)) {
|
||||||
.eq => {},
|
.eq => {},
|
||||||
@ -1550,6 +1593,10 @@ pub fn scrollClear(self: *PageList) !void {
|
|||||||
for (0..non_empty) |_| _ = try self.grow();
|
for (0..non_empty) |_| _ = try self.grow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maxSize(self: *const PageList) usize {
|
||||||
|
return @max(self.explicit_max_size, self.min_max_size);
|
||||||
|
}
|
||||||
|
|
||||||
/// Grow the active area by exactly one row.
|
/// Grow the active area by exactly one row.
|
||||||
///
|
///
|
||||||
/// This may allocate, but also may not if our current page has more
|
/// This may allocate, but also may not if our current page has more
|
||||||
@ -1570,7 +1617,7 @@ pub fn grow(self: *PageList) !?*List.Node {
|
|||||||
// If allocation would exceed our max size, we prune the first page.
|
// If allocation would exceed our max size, we prune the first page.
|
||||||
// We don't need to reallocate because we can simply reuse that first
|
// We don't need to reallocate because we can simply reuse that first
|
||||||
// page.
|
// page.
|
||||||
if (self.page_size + PagePool.item_size > self.max_size) {
|
if (self.page_size + PagePool.item_size > self.maxSize()) {
|
||||||
const layout = Page.layout(try std_capacity.adjust(.{ .cols = self.cols }));
|
const layout = Page.layout(try std_capacity.adjust(.{ .cols = self.cols }));
|
||||||
|
|
||||||
// Get our first page and reset it to prepare for reuse.
|
// Get our first page and reset it to prepare for reuse.
|
||||||
@ -1610,7 +1657,7 @@ pub fn grow(self: *PageList) !?*List.Node {
|
|||||||
|
|
||||||
// We should never be more than our max size here because we've
|
// We should never be more than our max size here because we've
|
||||||
// verified the case above.
|
// verified the case above.
|
||||||
assert(self.page_size <= self.max_size);
|
assert(self.page_size <= self.maxSize());
|
||||||
|
|
||||||
return next_page;
|
return next_page;
|
||||||
}
|
}
|
||||||
@ -4569,6 +4616,25 @@ test "PageList resize (no reflow) more rows and less cols" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList resize (no reflow) more rows and cols" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
|
||||||
|
// Resize to a size that requires more than one page to fit our rows.
|
||||||
|
const new_cols = 600;
|
||||||
|
const new_rows = 600;
|
||||||
|
const cap = try std_capacity.adjust(.{ .cols = new_cols });
|
||||||
|
try testing.expect(cap.rows < new_rows);
|
||||||
|
|
||||||
|
try s.resize(.{ .cols = new_cols, .rows = new_rows, .reflow = true });
|
||||||
|
try testing.expectEqual(@as(usize, new_cols), s.cols);
|
||||||
|
try testing.expectEqual(@as(usize, new_rows), s.rows);
|
||||||
|
try testing.expectEqual(@as(usize, new_rows), s.totalRows());
|
||||||
|
}
|
||||||
|
|
||||||
test "PageList resize (no reflow) empty screen" {
|
test "PageList resize (no reflow) empty screen" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
Reference in New Issue
Block a user