aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/hyperlink.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/terminal/hyperlink.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/hyperlink.zig')
-rw-r--r--vendor/libghostty-vt/src/terminal/hyperlink.zig240
1 files changed, 240 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/hyperlink.zig b/vendor/libghostty-vt/src/terminal/hyperlink.zig
new file mode 100644
index 00000000..94f86466
--- /dev/null
+++ b/vendor/libghostty-vt/src/terminal/hyperlink.zig
@@ -0,0 +1,240 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+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, since each cell can only contain
+/// at most one hyperlink.
+pub const Id = size.HyperlinkCountInt;
+
+// 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);
+
+/// A fully decoded hyperlink that may or may not have its
+/// memory within a page. The memory location of this is dependent
+/// on the context so users should check with the source of the
+/// hyperlink.
+pub const Hyperlink = struct {
+ id: Hyperlink.Id,
+ uri: []const u8,
+
+ /// See PageEntry.Id
+ pub const Id = union(enum) {
+ explicit: []const u8,
+ implicit: size.OffsetInt,
+ };
+
+ /// Deinit and deallocate all the pointers using the given
+ /// allocator.
+ ///
+ /// WARNING: This should only be called if the hyperlink was
+ /// heap-allocated. This DOES NOT need to be unconditionally
+ /// called.
+ pub fn deinit(self: *const Hyperlink, alloc: Allocator) void {
+ alloc.free(self.uri);
+ switch (self.id) {
+ .implicit => {},
+ .explicit => |v| alloc.free(v),
+ }
+ }
+
+ /// Duplicate a hyperlink by allocating all values with the
+ /// given allocator. The returned hyperlink should have deinit
+ /// called.
+ pub fn dupe(
+ self: *const Hyperlink,
+ alloc: Allocator,
+ ) Allocator.Error!Hyperlink {
+ const uri = try alloc.dupe(u8, self.uri);
+ errdefer alloc.free(uri);
+
+ const id: Hyperlink.Id = switch (self.id) {
+ .implicit => self.id,
+ .explicit => |v| .{ .explicit = try alloc.dupe(u8, v) },
+ };
+ errdefer switch (id) {
+ .implicit => {},
+ .explicit => |v| alloc.free(v),
+ };
+
+ return .{ .id = id, .uri = uri };
+ }
+};
+
+/// A hyperlink that has been committed to page memory. This
+/// is a "page entry" because while it represents a hyperlink,
+/// some decoding (pointer chasing) is still necessary to get the
+/// fully realized ID, URI, etc.
+pub const PageEntry = struct {
+ id: PageEntry.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 PageEntry,
+ self_page: *const Page,
+ dst_page: *Page,
+ ) error{OutOfMemory}!PageEntry {
+ 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.slice(self_page.memory);
+ 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.slice(dst_page.memory),
+ );
+
+ // Copy the ID
+ switch (copy.id) {
+ .implicit => {}, // Shallow is fine
+ .explicit => |slice| {
+ const id = slice.slice(self_page.memory);
+ 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.slice(dst_page.memory),
+ ),
+ };
+
+ return copy;
+ }
+
+ pub fn hash(self: *const PageEntry, 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.slice(base),
+ .Deep,
+ ),
+ }
+ autoHashStrat(
+ &hasher,
+ self.uri.slice(base),
+ .Deep,
+ );
+ return hasher.final();
+ }
+
+ pub fn eql(
+ self: *const PageEntry,
+ self_base: anytype,
+ other: *const PageEntry,
+ 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.slice(self_base),
+ other.uri.slice(other_base),
+ );
+ }
+
+ /// Free the memory for this entry from its page.
+ pub fn free(
+ self: *const PageEntry,
+ page: *Page,
+ ) void {
+ const alloc = &page.string_alloc;
+ switch (self.id) {
+ .implicit => {},
+ .explicit => |v| alloc.free(
+ page.memory,
+ v.slice(page.memory),
+ ),
+ }
+ alloc.free(
+ page.memory,
+ self.uri.slice(page.memory),
+ );
+ }
+};
+
+/// 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(
+ PageEntry,
+ Id,
+ size.CellCountInt,
+ struct {
+ /// The page which holds the strings for items in this set.
+ page: ?*Page = null,
+
+ /// The page which holds the strings for items
+ /// looked up with, e.g., `add` or `lookup`,
+ /// if different from the destination page.
+ src_page: ?*const Page = null,
+
+ pub fn hash(self: *const @This(), link: PageEntry) u64 {
+ return link.hash((self.src_page orelse self.page.?).memory);
+ }
+
+ pub fn eql(self: *const @This(), a: PageEntry, b: PageEntry) bool {
+ return a.eql(
+ (self.src_page orelse self.page.?).memory,
+ &b,
+ self.page.?.memory,
+ );
+ }
+
+ pub fn deleted(self: *const @This(), link: PageEntry) void {
+ link.free(self.page.?);
+ }
+ },
+);