terminal: bunch of junk for paged terminal

This commit is contained in:
Mitchell Hashimoto
2024-02-15 20:01:21 -08:00
parent badedf81a7
commit be8745c70a
4 changed files with 221 additions and 0 deletions

View File

@ -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");
}

95
src/terminal/new/page.zig Normal file
View File

@ -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;
}

60
src/terminal/new/size.zig Normal file
View File

@ -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));
}

View File

@ -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;
}