terminal/new: nothing works but everything looks right

This commit is contained in:
Mitchell Hashimoto
2024-02-17 18:49:57 -08:00
parent 6ffe66e728
commit 040d07d476
3 changed files with 165 additions and 8 deletions

View File

@ -2,8 +2,11 @@ const std = @import("std");
const assert = std.debug.assert;
const color = @import("../color.zig");
const sgr = @import("../sgr.zig");
const style = @import("style.zig");
const size = @import("size.zig");
const Offset = size.Offset;
const hash_map = @import("hash_map.zig");
const AutoOffsetHashMap = hash_map.AutoOffsetHashMap;
/// 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
@ -93,3 +96,14 @@ test {
_ = Page;
_ = Style;
}
// test {
// const testing = std.testing;
// const cap = try std.math.ceilPowerOfTwo(usize, 350);
// const StyleIdMap = AutoOffsetHashMap(size.CellCountInt, style.Style);
// const StyleMetadataMap = AutoOffsetHashMap(style.Style, style.Metadata);
//
// var len = StyleIdMap.bufferSize(@intCast(cap));
// len += StyleMetadataMap.bufferSize(@intCast(cap));
// try testing.expectEqual(@as(usize, 0), len);
// }

View File

@ -30,7 +30,7 @@ pub fn Offset(comptime T: type) type {
// 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);
return @ptrFromInt(intFromBase(base) + self.offset);
}
};
}
@ -42,12 +42,27 @@ pub fn getOffset(
base: anytype,
ptr: *const T,
) Offset(T) {
const base_int = @intFromPtr(base);
const base_int = intFromBase(base);
const ptr_int = @intFromPtr(ptr);
const offset = ptr_int - base_int;
return .{ .offset = @intCast(offset) };
}
fn intFromBase(base: anytype) usize {
return switch (@typeInfo(@TypeOf(base))) {
.Pointer => |v| switch (v.size) {
.One,
.Many,
.C,
=> @intFromPtr(base),
.Slice => @intFromPtr(base.ptr),
},
else => @compileError("invalid base type"),
};
}
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.

View File

@ -1,7 +1,11 @@
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;
const hash_map = @import("hash_map.zig");
const AutoOffsetHashMap = hash_map.AutoOffsetHashMap;
/// The unique identifier for a style. This is at most the number of cells
/// that can fit into a terminal page.
@ -43,20 +47,144 @@ pub const Style = struct {
}
};
/// A set of styles.
pub const Set = struct {
/// The mapping of a style to associated metadata. This is
/// the map that contains the actual style definitions
/// (in the form of the key).
styles: MetadataMap,
/// The mapping from ID to style.
id_map: IdMap,
/// The next ID to use for a style that isn't in the set.
/// When this overflows we'll begin returning an IdOverflow
/// error and the caller must manually compact the style
/// set.
next_id: Id = 1,
/// Maps a style definition to metadata about that style.
pub const MetadataMap = std.AutoHashMapUnmanaged(Style, Metadata);
const MetadataMap = AutoOffsetHashMap(Style, Metadata);
/// Maps the unique style ID to the concrete style definition.
pub const IdMap = std.AutoHashMapUnmanaged(size.CellCountInt, Style);
const IdMap = AutoOffsetHashMap(Id, Offset(Style));
/// Returns the memory layout for the given base offset and
/// desired capacity. The layout can be used by the caller to
/// determine how much memory to allocate, and the layout must
/// be used to initialize the set so that the set knows all
/// the offsets for the various buffers.
pub fn layoutForCapacity(base: usize, cap: usize) Layout {
const md_start = std.mem.alignForward(usize, base, MetadataMap.base_align);
const md_end = md_start + MetadataMap.bufferSize(@intCast(cap));
const id_start = std.mem.alignForward(usize, md_end, IdMap.base_align);
const id_end = id_start + IdMap.bufferSize(@intCast(cap));
const total_size = id_end - base;
return .{
.cap = cap,
.md_start = md_start,
.id_start = id_start,
.total_size = total_size,
};
}
pub const Layout = struct {
cap: usize,
md_start: usize,
id_start: usize,
total_size: usize,
};
pub fn init(base: []u8, layout: Layout) Set {
assert(base.len >= layout.total_size);
var styles = MetadataMap.init(@intCast(layout.cap), base[layout.md_start..]);
styles.metadata.offset += @intCast(layout.md_start);
var id_map = IdMap.init(@intCast(layout.cap), base[layout.id_start..]);
id_map.metadata.offset += @intCast(layout.id_start);
return .{
.styles = styles,
.id_map = id_map,
};
}
/// Upsert a style into the set and return a pointer to the metadata
/// for that style. The pointer is valid for the lifetime of the set
/// so long as the style is not removed.
pub fn upsert(self: *Set, base: anytype, style: Style) !*Metadata {
// If we already have the style in the map, this is fast.
var map = self.styles.map(base);
const gop = try map.getOrPut(style);
if (gop.found_existing) return gop.value_ptr;
// New style, we need to setup all the metadata. First thing,
// let's get the ID we'll assign, because if we're out of space
// we need to fail early.
errdefer map.removeByPtr(gop.key_ptr);
const id = self.next_id;
self.next_id = try std.math.add(Id, self.next_id, 1);
errdefer self.next_id -= 1;
gop.value_ptr.* = .{ .id = id };
// Setup our ID mapping
var id_map = self.id_map.map(base);
const id_gop = try id_map.getOrPut(id);
errdefer id_map.removeByPtr(id_gop.key_ptr);
assert(!id_gop.found_existing);
id_gop.value_ptr.* = size.getOffset(Style, base, gop.key_ptr);
return gop.value_ptr;
}
/// Lookup a style by its unique identifier.
pub fn lookupId(self: *const Set, base: anytype, id: Id) ?*Style {
const id_map = self.id_map.map(base);
const offset = id_map.get(id) orelse return null;
return @ptrCast(offset.ptr(base));
}
};
/// 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,
ref: size.CellCountInt = 1,
id: Id = 0,
};
test {
_ = Style;
_ = Set;
}
test "Set basic usage" {
const testing = std.testing;
const alloc = testing.allocator;
const layout = Set.layoutForCapacity(0, 16);
const buf = try alloc.alloc(u8, layout.total_size);
defer alloc.free(buf);
const style: Style = .{ .flags = .{ .bold = true } };
var set = Set.init(buf, layout);
// Upsert
const meta = try set.upsert(buf, style);
try testing.expect(meta.id > 0);
// Second upsert should return the same metadata.
{
const meta2 = try set.upsert(buf, style);
try testing.expectEqual(meta.id, meta2.id);
}
// Look it up
{
const v = set.lookupId(buf, meta.id).?;
try testing.expect(v.flags.bold);
}
}