mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00

Fixes #1991 To check if a hyperlink from another page is already present in our page's set, we need to dupe the hyperlink struct. If the hyperlink is already present in our page, this dupe is a waste and is freed. In the case where the hyperlink is present AND we don't have enough memory to dupe the hyperlink to check if its present, we'd previous simply crash out and fail rendering. Debug builds would crash with integrity errors. This commit resolves the issue by falling back to a slow path when our string allocation table is full and iterating over the hyperlink map to check one by one if we have the hyperlink. This O(N) is much slower than allocating (in this case) but N is usually low on top of this case being rare. A better solution would probably be to ensure we always have some % of available space free in our string allocation table. This would result in some wasteful page reallocs but would speed up the render loop. We can look into that later.
169 lines
5.5 KiB
Zig
169 lines
5.5 KiB
Zig
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const hash_map = @import("hash_map.zig");
|
|
const AutoOffsetHashMap = hash_map.AutoOffsetHashMap;
|
|
const pagepkg = @import("page.zig");
|
|
const size = @import("size.zig");
|
|
const Offset = size.Offset;
|
|
const Cell = pagepkg.Cell;
|
|
const Page = pagepkg.Page;
|
|
const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet;
|
|
const Wyhash = std.hash.Wyhash;
|
|
const autoHash = std.hash.autoHash;
|
|
const autoHashStrat = std.hash.autoHashStrat;
|
|
|
|
/// The unique identifier for a hyperlink. This is at most the number of cells
|
|
/// that can fit in a single terminal page.
|
|
pub const Id = size.CellCountInt;
|
|
|
|
// The mapping of cell to hyperlink. We use an offset hash map to save space
|
|
// since its very unlikely a cell is a hyperlink, so its a waste to store
|
|
// the hyperlink ID in the cell itself.
|
|
pub const Map = AutoOffsetHashMap(Offset(Cell), Id);
|
|
|
|
/// The main entry for hyperlinks.
|
|
pub const Hyperlink = struct {
|
|
id: Hyperlink.Id,
|
|
uri: Offset(u8).Slice,
|
|
|
|
pub const Id = union(enum) {
|
|
/// An explicitly provided ID via the OSC8 sequence.
|
|
explicit: Offset(u8).Slice,
|
|
|
|
/// No ID was provided so we auto-generate the ID based on an
|
|
/// incrementing counter attached to the screen.
|
|
implicit: size.OffsetInt,
|
|
};
|
|
|
|
/// Duplicate this hyperlink from one page to another.
|
|
pub fn dupe(
|
|
self: *const Hyperlink,
|
|
self_page: *const Page,
|
|
dst_page: *Page,
|
|
) error{OutOfMemory}!Hyperlink {
|
|
var copy = self.*;
|
|
|
|
// If the pages are the same then we can return a shallow copy.
|
|
if (self_page == dst_page) return copy;
|
|
|
|
// Copy the URI
|
|
{
|
|
const uri = self.uri.offset.ptr(self_page.memory)[0..self.uri.len];
|
|
const buf = try dst_page.string_alloc.alloc(u8, dst_page.memory, uri.len);
|
|
@memcpy(buf, uri);
|
|
copy.uri = .{
|
|
.offset = size.getOffset(u8, dst_page.memory, &buf[0]),
|
|
.len = uri.len,
|
|
};
|
|
}
|
|
errdefer dst_page.string_alloc.free(
|
|
dst_page.memory,
|
|
copy.uri.offset.ptr(dst_page.memory)[0..copy.uri.len],
|
|
);
|
|
|
|
// Copy the ID
|
|
switch (copy.id) {
|
|
.implicit => {}, // Shallow is fine
|
|
.explicit => |slice| {
|
|
const id = slice.offset.ptr(self_page.memory)[0..slice.len];
|
|
const buf = try dst_page.string_alloc.alloc(u8, dst_page.memory, id.len);
|
|
@memcpy(buf, id);
|
|
copy.id = .{ .explicit = .{
|
|
.offset = size.getOffset(u8, dst_page.memory, &buf[0]),
|
|
.len = id.len,
|
|
} };
|
|
},
|
|
}
|
|
errdefer switch (copy.id) {
|
|
.implicit => {},
|
|
.explicit => |v| dst_page.string_alloc.free(
|
|
dst_page.memory,
|
|
v.offset.ptr(dst_page.memory)[0..v.len],
|
|
),
|
|
};
|
|
|
|
return copy;
|
|
}
|
|
|
|
pub fn hash(self: *const Hyperlink, base: anytype) u64 {
|
|
var hasher = Wyhash.init(0);
|
|
autoHash(&hasher, std.meta.activeTag(self.id));
|
|
switch (self.id) {
|
|
.implicit => |v| autoHash(&hasher, v),
|
|
.explicit => |slice| autoHashStrat(
|
|
&hasher,
|
|
slice.offset.ptr(base)[0..slice.len],
|
|
.Deep,
|
|
),
|
|
}
|
|
autoHashStrat(
|
|
&hasher,
|
|
self.uri.offset.ptr(base)[0..self.uri.len],
|
|
.Deep,
|
|
);
|
|
return hasher.final();
|
|
}
|
|
|
|
pub fn eql(
|
|
self: *const Hyperlink,
|
|
self_base: anytype,
|
|
other: *const Hyperlink,
|
|
other_base: anytype,
|
|
) bool {
|
|
if (std.meta.activeTag(self.id) != std.meta.activeTag(other.id)) return false;
|
|
switch (self.id) {
|
|
.implicit => if (self.id.implicit != other.id.implicit) return false,
|
|
.explicit => {
|
|
const self_ptr = self.id.explicit.offset.ptr(self_base);
|
|
const other_ptr = other.id.explicit.offset.ptr(other_base);
|
|
if (!std.mem.eql(
|
|
u8,
|
|
self_ptr[0..self.id.explicit.len],
|
|
other_ptr[0..other.id.explicit.len],
|
|
)) return false;
|
|
},
|
|
}
|
|
|
|
return std.mem.eql(
|
|
u8,
|
|
self.uri.offset.ptr(self_base)[0..self.uri.len],
|
|
other.uri.offset.ptr(other_base)[0..other.uri.len],
|
|
);
|
|
}
|
|
};
|
|
|
|
/// The set of hyperlinks. This is ref-counted so that a set of cells
|
|
/// can share the same hyperlink without duplicating the data.
|
|
pub const Set = RefCountedSet(
|
|
Hyperlink,
|
|
Id,
|
|
size.CellCountInt,
|
|
struct {
|
|
page: ?*Page = null,
|
|
|
|
pub fn hash(self: *const @This(), link: Hyperlink) u64 {
|
|
return link.hash(self.page.?.memory);
|
|
}
|
|
|
|
pub fn eql(self: *const @This(), a: Hyperlink, b: Hyperlink) bool {
|
|
return a.eql(self.page.?.memory, &b, self.page.?.memory);
|
|
}
|
|
|
|
pub fn deleted(self: *const @This(), link: Hyperlink) void {
|
|
const page = self.page.?;
|
|
const alloc = &page.string_alloc;
|
|
switch (link.id) {
|
|
.implicit => {},
|
|
.explicit => |v| alloc.free(
|
|
page.memory,
|
|
v.offset.ptr(page.memory)[0..v.len],
|
|
),
|
|
}
|
|
alloc.free(
|
|
page.memory,
|
|
link.uri.offset.ptr(page.memory)[0..link.uri.len],
|
|
);
|
|
}
|
|
},
|
|
);
|