diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/terminal/hyperlink.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 240 |
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.?); + } + }, +); |
