diff --git a/src/terminal/main.zig b/src/terminal/main.zig index a4224e63a..03712b7fd 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -50,4 +50,8 @@ pub usingnamespace if (builtin.target.isWasm()) struct { test { @import("std").testing.refAllDecls(@This()); + + _ = @import("new/page.zig"); + _ = @import("new/size.zig"); + _ = @import("new/style.zig"); } diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig new file mode 100644 index 000000000..51851ec70 --- /dev/null +++ b/src/terminal/new/page.zig @@ -0,0 +1,95 @@ +const std = @import("std"); +const assert = std.debug.assert; +const color = @import("../color.zig"); +const sgr = @import("../sgr.zig"); +const size = @import("size.zig"); +const Offset = size.Offset; + +/// A page represents a specific section of terminal screen. The primary +/// idea of a page is that it is a fully self-contained unit that can be +/// serialized, copied, etc. as a convenient way to represent a section +/// of the screen. +/// +/// This property is useful for renderers which want to copy just the pages +/// for the visible portion of the screen, or for infinite scrollback where +/// we may want to serialize and store pages that are sufficiently far +/// away from the current viewport. +/// +/// Pages are always backed by a single contiguous block of memory that is +/// aligned on a page boundary. This makes it easy and fast to copy pages +/// around. Within the contiguous block of memory, the contents of a page are +/// thoughtfully laid out to optimize primarily for terminal IO (VT streams) +/// and to minimize memory usage. +pub const Page = struct { + /// The backing memory for the page. A page is always made up of a + /// a single contiguous block of memory that is aligned on a page + /// boundary and is a multiple of the system page size. + /// + /// The backing memory is always zero initialized, so the zero value + /// of all data within the page must always be valid. + memory: []align(std.mem.page_size) u8, + + /// The array of rows in the page. The rows are always in row order + /// (i.e. index 0 is the top row, index 1 is the row below that, etc.) + rows: Offset(Row), + + /// The array of cells in the page. The cells are NOT in row order, + /// but they are in column order. To determine the mapping of cells + /// to row, you must use the `rows` field. From the pointer to the + /// first column, all cells in that row are laid out in column order. + cells: Offset(Cell), +}; + +pub const Row = packed struct { + /// The cells in the row offset from the page. + cells: Offset(Cell), +}; + +/// A cell represents a single terminal grid cell. +/// +/// The zero value of this struct must be a valid cell representing empty, +/// since we zero initialize the backing memory for a page. +pub const Cell = packed struct(u32) { + codepoint: u21 = 0, +}; + +/// The style attributes for a cell. +pub const Style = struct { + /// Various colors, all self-explanatory. + fg_color: Color = .none, + bg_color: Color = .none, + underline_color: Color = .none, + + /// On/off attributes that don't require much bit width so we use + /// a packed struct to make this take up significantly less space. + flags: packed struct { + bold: bool = false, + italic: bool = false, + faint: bool = false, + blink: bool = false, + inverse: bool = false, + invisible: bool = false, + strikethrough: bool = false, + underline: sgr.Attribute.Underline = .none, + } = .{}, + + /// The color for an SGR attribute. A color can come from multiple + /// sources so we use this to track the source plus color value so that + /// we can properly react to things like palette changes. + pub const Color = union(enum) { + none: void, + palette: u8, + rgb: color.RGB, + }; + + test { + // The size of the struct so we can be aware of changes. + const testing = std.testing; + try testing.expectEqual(@as(usize, 14), @sizeOf(Style)); + } +}; + +test { + _ = Page; + _ = Style; +} diff --git a/src/terminal/new/size.zig b/src/terminal/new/size.zig new file mode 100644 index 000000000..4443a9291 --- /dev/null +++ b/src/terminal/new/size.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const assert = std.debug.assert; + +/// The maximum size of a page in bytes. We use a u16 here because any +/// smaller bit size by Zig is upgraded anyways to a u16 on mainstream +/// CPU architectures, and because 65KB is a reasonable page size. To +/// support better configurability, we derive everything from this. +pub const max_page_size = 65_536; + +/// The int type that can contain the maximum memory offset in bytes, +/// derived from the maximum terminal page size. +pub const OffsetInt = std.math.IntFittingRange(0, max_page_size - 1); + +/// The int type that can contain the maximum number of cells in a page. +pub const CellCountInt = u16; // TODO: derive +// +/// The offset from the base address of the page to the start of some data. +/// This is typed for ease of use. +/// +/// This is a packed struct so we can attach methods to an int. +pub fn Offset(comptime T: type) type { + return packed struct(OffsetInt) { + const Self = @This(); + + offset: OffsetInt = 0, + + /// Returns a pointer to the start of the data, properly typed. + pub fn ptr(self: Self, base: anytype) [*]T { + // The offset must be properly aligned for the type since + // our return type is naturally aligned. We COULD modify this + // to return arbitrary alignment, but its not something we need. + assert(@mod(self.offset, @alignOf(T)) == 0); + return @ptrFromInt(@intFromPtr(base) + self.offset); + } + }; +} + +test "Offset" { + // This test is here so that if Offset changes, we can be very aware + // of this effect and think about the implications of it. + const testing = std.testing; + try testing.expect(OffsetInt == u16); +} + +test "Offset ptr u8" { + const testing = std.testing; + const offset: Offset(u8) = .{ .offset = 42 }; + const base_int: usize = @intFromPtr(&offset); + const actual = offset.ptr(&offset); + try testing.expectEqual(@as(usize, base_int + 42), @intFromPtr(actual)); +} + +test "Offset ptr structural" { + const Struct = struct { x: u32, y: u32 }; + const testing = std.testing; + const offset: Offset(Struct) = .{ .offset = @alignOf(Struct) * 4 }; + const base_int: usize = @intFromPtr(&offset); + const actual = offset.ptr(&offset); + try testing.expectEqual(@as(usize, base_int + offset.offset), @intFromPtr(actual)); +} diff --git a/src/terminal/new/style.zig b/src/terminal/new/style.zig new file mode 100644 index 000000000..6fdb8c3e7 --- /dev/null +++ b/src/terminal/new/style.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const color = @import("../color.zig"); +const sgr = @import("../sgr.zig"); +const size = @import("size.zig"); + +/// 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; + +/// The style attributes for a cell. +pub const Style = struct { + /// Various colors, all self-explanatory. + fg_color: Color = .none, + bg_color: Color = .none, + underline_color: Color = .none, + + /// On/off attributes that don't require much bit width so we use + /// a packed struct to make this take up significantly less space. + flags: packed struct { + bold: bool = false, + italic: bool = false, + faint: bool = false, + blink: bool = false, + inverse: bool = false, + invisible: bool = false, + strikethrough: bool = false, + underline: sgr.Attribute.Underline = .none, + } = .{}, + + /// The color for an SGR attribute. A color can come from multiple + /// sources so we use this to track the source plus color value so that + /// we can properly react to things like palette changes. + pub const Color = union(enum) { + none: void, + palette: u8, + rgb: color.RGB, + }; + + test { + // The size of the struct so we can be aware of changes. + const testing = std.testing; + try testing.expectEqual(@as(usize, 14), @sizeOf(Style)); + } +}; + +/// Maps a style definition to metadata about that style. +pub const MetadataMap = std.AutoHashMapUnmanaged(Style, Metadata); + +/// Maps the unique style ID to the concrete style definition. +pub const IdMap = std.AutoHashMapUnmanaged(size.CellCountInt, Style); + +/// Metadata about a style. This is used to track the reference count +/// and the unique identifier for a style. The unique identifier is used +/// to track the style in the full style map. +pub const Metadata = struct { + ref: size.CellCountInt = 0, + id: size.CellCountInt = 0, +}; + +test { + _ = Style; +}