mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
terminal: add Capacity.maxCols
This commit is contained in:
@@ -49,7 +49,15 @@ const Node = struct {
|
||||
/// The memory pool we get page nodes from.
|
||||
const NodePool = std.heap.MemoryPool(List.Node);
|
||||
|
||||
/// The standard page capacity that we use as a starting point for
|
||||
/// all pages. This is chosen as a sane default that fits most terminal
|
||||
/// usage to support using our pool.
|
||||
const std_capacity = pagepkg.std_capacity;
|
||||
|
||||
/// The maximum columns we can support with the standard capacity.
|
||||
const std_max_cols = std_capacity.maxCols().?;
|
||||
|
||||
/// The byte size required for a standard page.
|
||||
const std_size = Page.layout(std_capacity).total_size;
|
||||
|
||||
/// The memory pool we use for page memory buffers. We use a separate pool
|
||||
|
||||
@@ -1662,43 +1662,42 @@ pub const Capacity = struct {
|
||||
cols: ?size.CellCountInt = null,
|
||||
};
|
||||
|
||||
/// Returns the maximum number of columns that can be used with this
|
||||
/// capacity while still fitting at least one row. Returns null if even
|
||||
/// a single column cannot fit (which would indicate an unusable capacity).
|
||||
///
|
||||
/// Note that this is the maximum number of columns that never increases
|
||||
/// the amount of memory the original capacity will take. If you modify
|
||||
/// the original capacity to add rows, then you can fit more columns.
|
||||
pub fn maxCols(self: Capacity) ?size.CellCountInt {
|
||||
const available_bits = self.availableBitsForGrid();
|
||||
|
||||
// If we can't even fit the row metadata, return null
|
||||
if (available_bits <= @bitSizeOf(Row)) return null;
|
||||
|
||||
// We do the math of how many columns we can fit in the remaining
|
||||
// bits ignoring the metadat of a row.
|
||||
const remaining_bits = available_bits - @bitSizeOf(Row);
|
||||
const max_cols = remaining_bits / @bitSizeOf(Cell);
|
||||
|
||||
// Clamp to CellCountInt max
|
||||
return @min(std.math.maxInt(size.CellCountInt), max_cols);
|
||||
}
|
||||
|
||||
/// Adjust the capacity parameters while retaining the same total size.
|
||||
///
|
||||
/// Adjustments always happen by limiting the rows in the page. Everything
|
||||
/// else can grow. If it is impossible to achieve the desired adjustment,
|
||||
/// OutOfMemory is returned.
|
||||
pub fn adjust(self: Capacity, req: Adjustment) Allocator.Error!Capacity {
|
||||
var adjusted = self;
|
||||
if (req.cols) |cols| {
|
||||
// The math below only works if there is no alignment gap between
|
||||
// the end of the rows array and the start of the cells array.
|
||||
//
|
||||
// To guarantee this, we assert that Row's size is a multiple of
|
||||
// Cell's alignment, so that any length array of Rows will end on
|
||||
// a valid alignment for the start of the Cell array.
|
||||
assert(@sizeOf(Row) % @alignOf(Cell) == 0);
|
||||
|
||||
const layout = Page.layout(self);
|
||||
|
||||
// In order to determine the amount of space in the page available
|
||||
// for rows & cells (which will allow us to calculate the number of
|
||||
// rows we can fit at a certain column width) we need to layout the
|
||||
// "meta" members of the page (i.e. everything else) from the end.
|
||||
const hyperlink_map_start = alignBackward(usize, layout.total_size - layout.hyperlink_map_layout.total_size, hyperlink.Map.base_align.toByteUnits());
|
||||
const hyperlink_set_start = alignBackward(usize, hyperlink_map_start - layout.hyperlink_set_layout.total_size, hyperlink.Set.base_align.toByteUnits());
|
||||
const string_alloc_start = alignBackward(usize, hyperlink_set_start - layout.string_alloc_layout.total_size, StringAlloc.base_align.toByteUnits());
|
||||
const grapheme_map_start = alignBackward(usize, string_alloc_start - layout.grapheme_map_layout.total_size, GraphemeMap.base_align.toByteUnits());
|
||||
const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - layout.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align.toByteUnits());
|
||||
const styles_start = alignBackward(usize, grapheme_alloc_start - layout.styles_layout.total_size, StyleSet.base_align.toByteUnits());
|
||||
const available_bits = self.availableBitsForGrid();
|
||||
|
||||
// The size per row is:
|
||||
// - The row metadata itself
|
||||
// - The cells per row (n=cols)
|
||||
const bits_per_row: usize = size: {
|
||||
var bits: usize = @bitSizeOf(Row); // Row metadata
|
||||
bits += @bitSizeOf(Cell) * @as(usize, @intCast(cols)); // Cells (n=cols)
|
||||
break :size bits;
|
||||
};
|
||||
const available_bits: usize = styles_start * 8;
|
||||
const bits_per_row: usize = @bitSizeOf(Row) + @bitSizeOf(Cell) * @as(usize, @intCast(cols));
|
||||
const new_rows: usize = @divFloor(available_bits, bits_per_row);
|
||||
|
||||
// If our rows go to zero then we can't fit any row metadata
|
||||
@@ -1711,6 +1710,34 @@ pub const Capacity = struct {
|
||||
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
/// Computes the number of bits available for rows and cells in the page.
|
||||
///
|
||||
/// This is done by laying out the "meta" members (styles, graphemes,
|
||||
/// hyperlinks, strings) from the end of the page and finding where they
|
||||
/// start, which gives us the space available for rows and cells.
|
||||
fn availableBitsForGrid(self: Capacity) usize {
|
||||
// The math below only works if there is no alignment gap between
|
||||
// the end of the rows array and the start of the cells array.
|
||||
//
|
||||
// To guarantee this, we assert that Row's size is a multiple of
|
||||
// Cell's alignment, so that any length array of Rows will end on
|
||||
// a valid alignment for the start of the Cell array.
|
||||
assert(@sizeOf(Row) % @alignOf(Cell) == 0);
|
||||
|
||||
const l = Page.layout(self);
|
||||
|
||||
// Layout meta members from the end to find styles_start
|
||||
const hyperlink_map_start = alignBackward(usize, l.total_size - l.hyperlink_map_layout.total_size, hyperlink.Map.base_align.toByteUnits());
|
||||
const hyperlink_set_start = alignBackward(usize, hyperlink_map_start - l.hyperlink_set_layout.total_size, hyperlink.Set.base_align.toByteUnits());
|
||||
const string_alloc_start = alignBackward(usize, hyperlink_set_start - l.string_alloc_layout.total_size, StringAlloc.base_align.toByteUnits());
|
||||
const grapheme_map_start = alignBackward(usize, string_alloc_start - l.grapheme_map_layout.total_size, GraphemeMap.base_align.toByteUnits());
|
||||
const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - l.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align.toByteUnits());
|
||||
const styles_start = alignBackward(usize, grapheme_alloc_start - l.styles_layout.total_size, StyleSet.base_align.toByteUnits());
|
||||
|
||||
// Multiply by 8 to convert bytes to bits
|
||||
return styles_start * 8;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Row = packed struct(u64) {
|
||||
@@ -2070,6 +2097,40 @@ test "Page capacity adjust cols too high" {
|
||||
);
|
||||
}
|
||||
|
||||
test "Capacity maxCols basic" {
|
||||
const cap = std_capacity;
|
||||
const max = cap.maxCols().?;
|
||||
|
||||
// maxCols should be >= current cols (since current capacity is valid)
|
||||
try testing.expect(max >= cap.cols);
|
||||
|
||||
// Adjusting to maxCols should succeed with at least 1 row
|
||||
const adjusted = try cap.adjust(.{ .cols = max });
|
||||
try testing.expect(adjusted.rows >= 1);
|
||||
|
||||
// Adjusting to maxCols + 1 should fail
|
||||
try testing.expectError(
|
||||
error.OutOfMemory,
|
||||
cap.adjust(.{ .cols = max + 1 }),
|
||||
);
|
||||
}
|
||||
|
||||
test "Capacity maxCols preserves total size" {
|
||||
const cap = std_capacity;
|
||||
const original_size = Page.layout(cap).total_size;
|
||||
const max = cap.maxCols().?;
|
||||
const adjusted = try cap.adjust(.{ .cols = max });
|
||||
const adjusted_size = Page.layout(adjusted).total_size;
|
||||
try testing.expectEqual(original_size, adjusted_size);
|
||||
}
|
||||
|
||||
test "Capacity maxCols with 1 row exactly" {
|
||||
const cap = std_capacity;
|
||||
const max = cap.maxCols().?;
|
||||
const adjusted = try cap.adjust(.{ .cols = max });
|
||||
try testing.expectEqual(@as(size.CellCountInt, 1), adjusted.rows);
|
||||
}
|
||||
|
||||
test "Page init" {
|
||||
var page = try Page.init(.{
|
||||
.cols = 120,
|
||||
|
||||
Reference in New Issue
Block a user