mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal: support page oom with hyperlinks
This commit is contained in:
@ -1849,6 +1849,12 @@ pub const AdjustCapacity = struct {
|
|||||||
|
|
||||||
/// Adjust the number of available grapheme bytes in the page.
|
/// Adjust the number of available grapheme bytes in the page.
|
||||||
grapheme_bytes: ?usize = null,
|
grapheme_bytes: ?usize = null,
|
||||||
|
|
||||||
|
/// Adjust the number of available hyperlink bytes in the page.
|
||||||
|
hyperlink_bytes: ?usize = null,
|
||||||
|
|
||||||
|
/// Adjust the number of available string bytes in the page.
|
||||||
|
string_bytes: ?usize = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Adjust the capcaity of the given page in the list. This should
|
/// Adjust the capcaity of the given page in the list. This should
|
||||||
@ -1884,6 +1890,14 @@ pub fn adjustCapacity(
|
|||||||
const aligned = try std.math.ceilPowerOfTwo(usize, v);
|
const aligned = try std.math.ceilPowerOfTwo(usize, v);
|
||||||
cap.grapheme_bytes = @max(cap.grapheme_bytes, aligned);
|
cap.grapheme_bytes = @max(cap.grapheme_bytes, aligned);
|
||||||
}
|
}
|
||||||
|
if (adjustment.hyperlink_bytes) |v| {
|
||||||
|
const aligned = try std.math.ceilPowerOfTwo(usize, v);
|
||||||
|
cap.hyperlink_bytes = @max(cap.hyperlink_bytes, aligned);
|
||||||
|
}
|
||||||
|
if (adjustment.string_bytes) |v| {
|
||||||
|
const aligned = try std.math.ceilPowerOfTwo(usize, v);
|
||||||
|
cap.string_bytes = @max(cap.string_bytes, aligned);
|
||||||
|
}
|
||||||
|
|
||||||
log.info("adjusting page capacity={}", .{cap});
|
log.info("adjusting page capacity={}", .{cap});
|
||||||
|
|
||||||
@ -4040,6 +4054,49 @@ test "PageList adjustCapacity to increase graphemes" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList adjustCapacity to increase hyperlinks" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 2, 2, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
{
|
||||||
|
try testing.expect(s.pages.first == s.pages.last);
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
|
||||||
|
// Write all our data so we can assert its the same after
|
||||||
|
for (0..s.rows) |y| {
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const rac = page.getRowAndCell(x, y);
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = @intCast(x) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase our graphemes
|
||||||
|
_ = try s.adjustCapacity(
|
||||||
|
s.pages.first.?,
|
||||||
|
.{ .hyperlink_bytes = @max(std_capacity.hyperlink_bytes * 2, 2048) },
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
try testing.expect(s.pages.first == s.pages.last);
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
for (0..s.rows) |y| {
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const rac = page.getRowAndCell(x, y);
|
||||||
|
try testing.expectEqual(
|
||||||
|
@as(u21, @intCast(x)),
|
||||||
|
rac.cell.content.codepoint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "PageList pageIterator single page" {
|
test "PageList pageIterator single page" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -1380,19 +1380,65 @@ pub fn startHyperlink(
|
|||||||
self: *Screen,
|
self: *Screen,
|
||||||
uri: []const u8,
|
uri: []const u8,
|
||||||
id_: ?[]const u8,
|
id_: ?[]const u8,
|
||||||
|
) !void {
|
||||||
|
// Loop until we have enough page memory to add the hyperlink
|
||||||
|
while (true) {
|
||||||
|
if (self.startHyperlinkOnce(uri, id_)) {
|
||||||
|
return;
|
||||||
|
} else |err| switch (err) {
|
||||||
|
// An actual self.alloc OOM is a fatal error.
|
||||||
|
error.RealOutOfMemory => return error.OutOfMemory,
|
||||||
|
|
||||||
|
// strings table is out of memory, adjust it up
|
||||||
|
error.StringsOutOfMemory => _ = try self.pages.adjustCapacity(
|
||||||
|
self.cursor.page_pin.page,
|
||||||
|
.{ .string_bytes = self.cursor.page_pin.page.data.capacity.string_bytes * 2 },
|
||||||
|
),
|
||||||
|
|
||||||
|
// hyperlink set is out of memory, adjust it up
|
||||||
|
error.SetOutOfMemory => _ = try self.pages.adjustCapacity(
|
||||||
|
self.cursor.page_pin.page,
|
||||||
|
.{ .hyperlink_bytes = self.cursor.page_pin.page.data.capacity.hyperlink_bytes * 2 },
|
||||||
|
),
|
||||||
|
|
||||||
|
// hyperlink set is too full, rehash it
|
||||||
|
error.SetNeedsRehash => _ = try self.pages.adjustCapacity(
|
||||||
|
self.cursor.page_pin.page,
|
||||||
|
.{},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, we adjusted capacity so our page has changed
|
||||||
|
// so we need to reload the cursor pins.
|
||||||
|
self.cursorReload();
|
||||||
|
self.assertIntegrity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is like startHyperlink but if we have to adjust page capacities
|
||||||
|
/// this returns error.PageAdjusted. This is useful so that we unwind
|
||||||
|
/// all the previous state and try again.
|
||||||
|
fn startHyperlinkOnce(
|
||||||
|
self: *Screen,
|
||||||
|
uri: []const u8,
|
||||||
|
id_: ?[]const u8,
|
||||||
) !void {
|
) !void {
|
||||||
// End any prior hyperlink
|
// End any prior hyperlink
|
||||||
self.endHyperlink();
|
self.endHyperlink();
|
||||||
|
|
||||||
// Create our hyperlink state.
|
// Create our hyperlink state.
|
||||||
const link = try Hyperlink.create(self.alloc, uri, id_);
|
const link = Hyperlink.create(self.alloc, uri, id_) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.RealOutOfMemory,
|
||||||
|
};
|
||||||
errdefer link.destroy(self.alloc);
|
errdefer link.destroy(self.alloc);
|
||||||
|
|
||||||
// Copy our URI into the page memory.
|
// Copy our URI into the page memory.
|
||||||
var page = &self.cursor.page_pin.page.data;
|
var page = &self.cursor.page_pin.page.data;
|
||||||
const string_alloc = &page.string_alloc;
|
const string_alloc = &page.string_alloc;
|
||||||
const page_uri: Offset(u8).Slice = uri: {
|
const page_uri: Offset(u8).Slice = uri: {
|
||||||
const buf = try string_alloc.alloc(u8, page.memory, uri.len);
|
const buf = string_alloc.alloc(u8, page.memory, uri.len) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.StringsOutOfMemory,
|
||||||
|
};
|
||||||
errdefer string_alloc.free(page.memory, buf);
|
errdefer string_alloc.free(page.memory, buf);
|
||||||
@memcpy(buf, uri);
|
@memcpy(buf, uri);
|
||||||
|
|
||||||
@ -1408,7 +1454,9 @@ pub fn startHyperlink(
|
|||||||
|
|
||||||
// Copy our ID into page memory or create an implicit ID via the counter
|
// Copy our ID into page memory or create an implicit ID via the counter
|
||||||
const page_id: hyperlink.Hyperlink.Id = if (id_) |id| explicit: {
|
const page_id: hyperlink.Hyperlink.Id = if (id_) |id| explicit: {
|
||||||
const buf = try string_alloc.alloc(u8, page.memory, id.len);
|
const buf = string_alloc.alloc(u8, page.memory, id.len) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.StringsOutOfMemory,
|
||||||
|
};
|
||||||
errdefer string_alloc.free(page.memory, buf);
|
errdefer string_alloc.free(page.memory, buf);
|
||||||
@memcpy(buf, id);
|
@memcpy(buf, id);
|
||||||
|
|
||||||
@ -1431,11 +1479,14 @@ pub fn startHyperlink(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Put our hyperlink into the hyperlink set to get an ID
|
// Put our hyperlink into the hyperlink set to get an ID
|
||||||
const id = try page.hyperlink_set.addContext(
|
const id = page.hyperlink_set.addContext(
|
||||||
page.memory,
|
page.memory,
|
||||||
.{ .id = page_id, .uri = page_uri },
|
.{ .id = page_id, .uri = page_uri },
|
||||||
.{ .page = page },
|
.{ .page = page },
|
||||||
);
|
) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.SetOutOfMemory,
|
||||||
|
error.NeedsRehash => return error.SetNeedsRehash,
|
||||||
|
};
|
||||||
errdefer page.hyperlink_set.release(page.memory, id);
|
errdefer page.hyperlink_set.release(page.memory, id);
|
||||||
|
|
||||||
// Save it all
|
// Save it all
|
||||||
|
@ -58,7 +58,7 @@ const string_bytes_default = string_count_default * string_chunk;
|
|||||||
/// The cell multiplier is the number of cells per hyperlink entry that
|
/// The cell multiplier is the number of cells per hyperlink entry that
|
||||||
/// we support. A hyperlink can be longer than this multiplier; the multiplier
|
/// we support. A hyperlink can be longer than this multiplier; the multiplier
|
||||||
/// just sets the total capacity to simplify adjustable size metrics.
|
/// just sets the total capacity to simplify adjustable size metrics.
|
||||||
const hyperlink_count_default = 32;
|
const hyperlink_count_default = 4;
|
||||||
const hyperlink_bytes_default = hyperlink_count_default * @sizeOf(hyperlink.Set.Item);
|
const hyperlink_bytes_default = hyperlink_count_default * @sizeOf(hyperlink.Set.Item);
|
||||||
const hyperlink_cell_multiplier = 16;
|
const hyperlink_cell_multiplier = 16;
|
||||||
|
|
||||||
@ -681,6 +681,8 @@ pub const Page = struct {
|
|||||||
.{ .page = self },
|
.{ .page = self },
|
||||||
);
|
);
|
||||||
try self.setHyperlink(dst_row, dst_cell, dst_id);
|
try self.setHyperlink(dst_row, dst_cell, dst_id);
|
||||||
|
|
||||||
|
// TODO: copy the strings
|
||||||
}
|
}
|
||||||
if (src_cell.style_id != style.default_id) {
|
if (src_cell.style_id != style.default_id) {
|
||||||
dst_row.styled = true;
|
dst_row.styled = true;
|
||||||
|
Reference in New Issue
Block a user