diff --git a/src/terminal/hyperlink.zig b/src/terminal/hyperlink.zig new file mode 100644 index 000000000..d7e59914a --- /dev/null +++ b/src/terminal/hyperlink.zig @@ -0,0 +1,51 @@ +const std = @import("std"); +const assert = std.debug.assert; +const hash_map = @import("hash_map.zig"); +const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; +const size = @import("size.zig"); +const Offset = size.Offset; +const Cell = @import("page.zig").Cell; +const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet; + +/// 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: 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. TODO: implement the counter + implicit: size.OffsetInt, + }, + + /// The URI for the actual link. + uri: Offset(u8).Slice, +}; + +/// 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 { + pub fn hash(self: *const @This(), link: Hyperlink) u64 { + _ = self; + return link.hash(); + } + + pub fn eql(self: *const @This(), a: Hyperlink, b: Hyperlink) bool { + _ = self; + return a.eql(b); + } + }, +); diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 8f895a82c..da3b0d2f5 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -7,6 +7,7 @@ const testing = std.testing; const posix = std.posix; const fastmem = @import("../fastmem.zig"); const color = @import("color.zig"); +const hyperlink = @import("hyperlink.zig"); const sgr = @import("sgr.zig"); const style = @import("style.zig"); const size = @import("size.zig"); @@ -114,6 +115,13 @@ pub const Page = struct { /// The available set of styles in use on this page. styles: style.Set, + /// The structures used for tracking hyperlinks within the page. + /// The map maps cell offsets to hyperlink IDs and the IDs are in + /// the ref counted set. The strings within the hyperlink structures + /// are allocated in the string allocator. + hyperlink_map: hyperlink.Map, + hyperlink_set: hyperlink.Set, + /// The offset to the first mask of dirty bits in the page. /// /// The dirty bits is a contiguous array of usize where each bit represents @@ -234,6 +242,15 @@ pub const Page = struct { buf.add(l.grapheme_map_start), l.grapheme_map_layout, ), + .hyperlink_map = hyperlink.Map.init( + buf.add(l.hyperlink_map_start), + l.hyperlink_map_layout, + ), + .hyperlink_set = hyperlink.Set.init( + buf.add(l.hyperlink_set_start), + l.hyperlink_set_layout, + .{}, + ), .size = .{ .cols = cap.cols, .rows = cap.rows }, .capacity = cap, }; @@ -1006,6 +1023,10 @@ pub const Page = struct { grapheme_map_layout: GraphemeMap.Layout, string_alloc_start: usize, string_alloc_layout: StringAlloc.Layout, + hyperlink_map_start: usize, + hyperlink_map_layout: hyperlink.Map.Layout, + hyperlink_set_start: usize, + hyperlink_set_layout: hyperlink.Set.Layout, capacity: Capacity, }; @@ -1048,7 +1069,15 @@ pub const Page = struct { const string_start = alignForward(usize, grapheme_map_end, StringAlloc.base_align); const string_end = string_start + string_layout.total_size; - const total_size = alignForward(usize, string_end, std.mem.page_size); + const hyperlink_map_layout = hyperlink.Map.layout(@intCast(cap.hyperlink_cells)); + const hyperlink_map_start = alignForward(usize, string_end, hyperlink.Map.base_align); + const hyperlink_map_end = hyperlink_map_start + hyperlink_map_layout.total_size; + + const hyperlink_set_layout = hyperlink.Set.layout(@intCast(cap.hyperlink_entries)); + const hyperlink_set_start = alignForward(usize, hyperlink_map_end, hyperlink.Set.base_align); + const hyperlink_set_end = hyperlink_set_start + hyperlink_set_layout.total_size; + + const total_size = alignForward(usize, hyperlink_set_end, std.mem.page_size); return .{ .total_size = total_size, @@ -1066,6 +1095,10 @@ pub const Page = struct { .grapheme_map_layout = grapheme_map_layout, .string_alloc_start = string_start, .string_alloc_layout = string_layout, + .hyperlink_map_start = hyperlink_map_start, + .hyperlink_map_layout = hyperlink_map_layout, + .hyperlink_set_start = hyperlink_set_start, + .hyperlink_set_layout = hyperlink_set_layout, .capacity = cap, }; } @@ -1080,6 +1113,8 @@ pub const std_capacity: Capacity = .{ .cols = 215, .rows = 215, .styles = 128, + .hyperlink_cells = 32, // TODO: think about these numbers + .hyperlink_entries = 4, .grapheme_bytes = 8192, .string_bytes = 2048, }; @@ -1099,6 +1134,12 @@ pub const Capacity = struct { /// Number of unique styles that can be used on this page. styles: usize = 16, + /// The cells is the number of cells in the terminal that can have + /// a hyperlink and the entries is the number of unique hyperlinks + /// itself. + hyperlink_cells: usize = 32, + hyperlink_entries: usize = 4, + /// Number of bytes to allocate for grapheme data. grapheme_bytes: usize = grapheme_bytes_default, @@ -1130,7 +1171,9 @@ pub const Capacity = struct { // 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 string_alloc_start = alignBackward(usize, layout.total_size, StringAlloc.base_align); + const hyperlink_set_start = alignBackward(usize, layout.total_size - layout.hyperlink_set_layout.total_size, hyperlink.Set.base_align); + const hyperlink_map_start = alignBackward(usize, hyperlink_set_start - layout.hyperlink_map_layout.total_size, hyperlink.Map.base_align); + const string_alloc_start = alignBackward(usize, hyperlink_map_start - layout.string_alloc_layout.total_size, StringAlloc.base_align); const grapheme_map_start = alignBackward(usize, string_alloc_start - layout.grapheme_map_layout.total_size, GraphemeMap.base_align); const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - layout.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align); const styles_start = alignBackward(usize, grapheme_alloc_start - layout.styles_layout.total_size, style.Set.base_align); diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 813411a73..430fca214 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -6,12 +6,11 @@ const page = @import("page.zig"); const size = @import("size.zig"); const Offset = size.Offset; const OffsetBuf = size.OffsetBuf; +const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet; const Wyhash = std.hash.Wyhash; const autoHash = std.hash.autoHash; -const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet; - /// The unique identifier for a style. This is at most the number of cells /// that can fit into a terminal page. pub const Id = size.CellCountInt;