Current section
Files
Jump to
Current section
Files
zig/opentui/tests/rope-nested_test.zig
const std = @import("std");
const rope_mod = @import("../rope.zig");
// NOTE: This is not strictly necessary to be supported as it is not used this way in the codebase.
// It can be removed if the rope needs to change in a way that would break this behavior.
const Chunk = struct {
data: []const u8,
width: u32,
pub const Metrics = struct {
total_width: u32 = 0,
total_bytes: u32 = 0,
pub fn add(self: *Metrics, other: Metrics) void {
self.total_width += other.total_width;
self.total_bytes += other.total_bytes;
}
};
pub fn measure(self: *const Chunk) Metrics {
return .{
.total_width = self.width,
.total_bytes = @intCast(self.data.len),
};
}
pub fn empty() Chunk {
return .{ .data = "", .width = 0 };
}
pub fn is_empty(self: *const Chunk) bool {
return self.data.len == 0;
}
};
// Static empty chunk rope node for Line.empty()
const empty_chunk_leaf_node = rope_mod.Rope(Chunk).Node{
.leaf = .{
.data = Chunk.empty(),
},
};
// Line type containing a rope of chunks
const Line = struct {
chunks: rope_mod.Rope(Chunk),
line_id: u32,
pub const Metrics = struct {
total_width: u32 = 0,
total_lines: u32 = 1,
pub fn add(self: *Metrics, other: Metrics) void {
self.total_width += other.total_width;
self.total_lines += other.total_lines;
}
};
pub fn measure(self: *const Line) Metrics {
const chunk_metrics = self.chunks.root.metrics();
return .{
.total_width = chunk_metrics.custom.total_width,
.total_lines = 1,
};
}
pub fn empty() Line {
// Use static empty chunk rope - safe because it's immutable
const ChunkRope = rope_mod.Rope(Chunk);
return .{
.chunks = .{
.root = &empty_chunk_leaf_node,
.allocator = undefined, // Never used for empty
.empty_leaf = &empty_chunk_leaf_node,
.marker_cache = ChunkRope.MarkerCache.init(undefined),
},
.line_id = 0,
};
}
pub fn is_empty(self: *const Line) bool {
return self.line_id == 0 and self.chunks.count() == 1;
}
};
test "Nested Rope - create line with chunks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks = [_]Chunk{
.{ .data = "Hello ", .width = 6 },
.{ .data = "World", .width = 5 },
};
const chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks);
const line = Line{
.chunks = chunk_rope,
.line_id = 1,
};
try std.testing.expectEqual(@as(u32, 2), line.chunks.count());
try std.testing.expectEqualStrings("Hello ", line.chunks.get(0).?.data);
try std.testing.expectEqualStrings("World", line.chunks.get(1).?.data);
const metrics = line.measure();
try std.testing.expectEqual(@as(u32, 11), metrics.total_width); // 6 + 5
}
test "Nested Rope - rope of lines with chunks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{
.{ .data = "Line ", .width = 5 },
.{ .data = "One", .width = 3 },
};
const line1 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1),
.line_id = 1,
};
const chunks2 = [_]Chunk{
.{ .data = "Line ", .width = 5 },
.{ .data = "Two", .width = 3 },
};
const line2 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2),
.line_id = 2,
};
const lines = [_]Line{ line1, line2 };
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
try std.testing.expectEqual(@as(u32, 2), line_rope.count());
// Access nested data
const first_line = line_rope.get(0).?;
try std.testing.expectEqual(@as(u32, 1), first_line.line_id);
try std.testing.expectEqual(@as(u32, 2), first_line.chunks.count());
try std.testing.expectEqualStrings("Line ", first_line.chunks.get(0).?.data);
const second_line = line_rope.get(1).?;
try std.testing.expectEqual(@as(u32, 2), second_line.line_id);
try std.testing.expectEqualStrings("Two", second_line.chunks.get(1).?.data);
}
test "Nested Rope - insert chunk into line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks = [_]Chunk{
.{ .data = "Hello ", .width = 6 },
.{ .data = "World", .width = 5 },
};
var chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks);
try chunk_rope.insert(1, .{ .data = "Beautiful ", .width = 10 });
try std.testing.expectEqual(@as(u32, 3), chunk_rope.count());
try std.testing.expectEqualStrings("Hello ", chunk_rope.get(0).?.data);
try std.testing.expectEqualStrings("Beautiful ", chunk_rope.get(1).?.data);
try std.testing.expectEqualStrings("World", chunk_rope.get(2).?.data);
const metrics = chunk_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 21), metrics.custom.total_width); // 6 + 10 + 5
}
test "Nested Rope - delete chunk from line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks = [_]Chunk{
.{ .data = "A ", .width = 2 },
.{ .data = "B ", .width = 2 },
.{ .data = "C", .width = 1 },
};
var chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks);
try chunk_rope.delete(1);
try std.testing.expectEqual(@as(u32, 2), chunk_rope.count());
try std.testing.expectEqualStrings("A ", chunk_rope.get(0).?.data);
try std.testing.expectEqualStrings("C", chunk_rope.get(1).?.data);
const metrics = chunk_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 3), metrics.custom.total_width); // 2 + 1
}
test "Nested Rope - walk through lines and chunks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{.{ .data = "Line1", .width = 5 }};
const chunks2 = [_]Chunk{.{ .data = "Line2", .width = 5 }};
const chunks3 = [_]Chunk{.{ .data = "Line3", .width = 5 }};
const lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2), .line_id = 2 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks3), .line_id = 3 },
};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
const LineContext = struct {
total_lines: u32 = 0,
total_width: u32 = 0,
fn walker(ctx: *anyopaque, line: *const Line, index: u32) rope_mod.Rope(Line).Node.WalkerResult {
_ = index;
const self = @as(*@This(), @ptrCast(@alignCast(ctx)));
self.total_lines += 1;
const metrics = line.chunks.root.metrics();
self.total_width += metrics.custom.total_width;
return .{};
}
};
var ctx = LineContext{};
try line_rope.walk(&ctx, LineContext.walker);
try std.testing.expectEqual(@as(u32, 3), ctx.total_lines);
try std.testing.expectEqual(@as(u32, 15), ctx.total_width); // 5 + 5 + 5
}
test "Nested Rope - complex line and chunk operations" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{
.{ .data = "First ", .width = 6 },
.{ .data = "line", .width = 4 },
};
const line1 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1),
.line_id = 1,
};
var line_rope = try rope_mod.Rope(Line).from_item(allocator, line1);
const chunks2 = [_]Chunk{.{ .data = "Second line", .width = 11 }};
const line2 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2),
.line_id = 2,
};
try line_rope.append(line2);
try std.testing.expectEqual(@as(u32, 2), line_rope.count());
// Access specific chunk in specific line
const first_line = line_rope.get(0).?;
try std.testing.expectEqual(@as(u32, 2), first_line.chunks.count());
try std.testing.expectEqualStrings("First ", first_line.chunks.get(0).?.data);
const metrics = line_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 2), metrics.count);
}
test "Nested Rope - metrics propagate through all levels" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{
.{ .data = "abc", .width = 3 },
.{ .data = "def", .width = 3 },
};
const chunks2 = [_]Chunk{
.{ .data = "12345", .width = 5 },
};
const chunks3 = [_]Chunk{
.{ .data = "x", .width = 1 },
.{ .data = "y", .width = 1 },
.{ .data = "z", .width = 1 },
};
const lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2), .line_id = 2 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks3), .line_id = 3 },
};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
const line_metrics = line_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 3), line_metrics.count);
const line1 = line_rope.get(0).?;
const line1_metrics = line1.measure();
try std.testing.expectEqual(@as(u32, 6), line1_metrics.total_width); // 3 + 3
const line2 = line_rope.get(1).?;
const line2_metrics = line2.measure();
try std.testing.expectEqual(@as(u32, 5), line2_metrics.total_width);
const line3 = line_rope.get(2).?;
const line3_metrics = line3.measure();
try std.testing.expectEqual(@as(u32, 3), line3_metrics.total_width); // 1 + 1 + 1
}
test "Nested Rope - simulate text buffer edit: insert within line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const initial_chunks = [_]Chunk{
.{ .data = "Hello ", .width = 6 },
.{ .data = "World", .width = 5 },
};
var chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &initial_chunks);
try chunk_rope.insert(1, .{ .data = "Beautiful ", .width = 10 });
try std.testing.expectEqual(@as(u32, 3), chunk_rope.count());
try std.testing.expectEqualStrings("Hello ", chunk_rope.get(0).?.data);
try std.testing.expectEqualStrings("Beautiful ", chunk_rope.get(1).?.data);
try std.testing.expectEqualStrings("World", chunk_rope.get(2).?.data);
const metrics = chunk_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 21), metrics.custom.total_width);
}
test "Nested Rope - simulate text buffer edit: delete within line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const initial_chunks = [_]Chunk{
.{ .data = "Hello ", .width = 6 },
.{ .data = "Beautiful ", .width = 10 },
.{ .data = "World", .width = 5 },
};
var chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &initial_chunks);
try chunk_rope.delete(1);
try std.testing.expectEqual(@as(u32, 2), chunk_rope.count());
try std.testing.expectEqualStrings("Hello ", chunk_rope.get(0).?.data);
try std.testing.expectEqualStrings("World", chunk_rope.get(1).?.data);
const metrics = chunk_rope.root.metrics();
try std.testing.expectEqual(@as(u32, 11), metrics.custom.total_width);
}
test "Nested Rope - insert line into document" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{.{ .data = "Line 1", .width = 6 }};
const chunks3 = [_]Chunk{.{ .data = "Line 3", .width = 6 }};
const initial_lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks3), .line_id = 3 },
};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &initial_lines);
const chunks2 = [_]Chunk{.{ .data = "Line 2", .width = 6 }};
const line2 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2),
.line_id = 2,
};
try line_rope.insert(1, line2);
try std.testing.expectEqual(@as(u32, 3), line_rope.count());
try std.testing.expectEqual(@as(u32, 1), line_rope.get(0).?.line_id);
try std.testing.expectEqual(@as(u32, 2), line_rope.get(1).?.line_id);
try std.testing.expectEqual(@as(u32, 3), line_rope.get(2).?.line_id);
}
test "Nested Rope - delete line from document" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{.{ .data = "Line 1", .width = 6 }};
const chunks2 = [_]Chunk{.{ .data = "Line 2", .width = 6 }};
const chunks3 = [_]Chunk{.{ .data = "Line 3", .width = 6 }};
const lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2), .line_id = 2 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks3), .line_id = 3 },
};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
try line_rope.delete(1);
try std.testing.expectEqual(@as(u32, 2), line_rope.count());
try std.testing.expectEqual(@as(u32, 1), line_rope.get(0).?.line_id);
try std.testing.expectEqual(@as(u32, 3), line_rope.get(1).?.line_id);
}
test "Nested Rope - modify chunks within a specific line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const initial_chunks = [_]Chunk{
.{ .data = "Hello", .width = 5 },
};
var chunk_rope = try rope_mod.Rope(Chunk).from_slice(allocator, &initial_chunks);
try chunk_rope.insert(1, .{ .data = " World", .width = 6 });
const line = Line{
.chunks = chunk_rope,
.line_id = 1,
};
const lines = [_]Line{line};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
const retrieved_line = line_rope.get(0).?;
try std.testing.expectEqual(@as(u32, 2), retrieved_line.chunks.count());
try std.testing.expectEqualStrings("Hello", retrieved_line.chunks.get(0).?.data);
try std.testing.expectEqualStrings(" World", retrieved_line.chunks.get(1).?.data);
const line_metrics = retrieved_line.measure();
try std.testing.expectEqual(@as(u32, 11), line_metrics.total_width);
}
test "Nested Rope - walk all chunks in all lines" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{
.{ .data = "A", .width = 1 },
.{ .data = "B", .width = 1 },
};
const chunks2 = [_]Chunk{
.{ .data = "C", .width = 1 },
.{ .data = "D", .width = 1 },
.{ .data = "E", .width = 1 },
};
const lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2), .line_id = 2 },
};
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
var total_chunks: u32 = 0;
var total_bytes: u32 = 0;
const LineWalker = struct {
fn walk(ctx: *anyopaque, line: *const Line, index: u32) rope_mod.Rope(Line).Node.WalkerResult {
_ = index;
const counters = @as(*[2]u32, @ptrCast(@alignCast(ctx)));
// Count chunks in this line
const ChunkWalker = struct {
fn walk(chunk_ctx: *anyopaque, chunk: *const Chunk, chunk_idx: u32) rope_mod.Rope(Chunk).Node.WalkerResult {
_ = chunk_idx;
const chunk_counters = @as(*[2]u32, @ptrCast(@alignCast(chunk_ctx)));
chunk_counters[0] += 1; // total_chunks
chunk_counters[1] += @intCast(chunk.data.len); // total_bytes
return .{};
}
};
line.chunks.walk(counters, ChunkWalker.walk) catch {};
return .{};
}
};
var counters = [2]u32{ total_chunks, total_bytes };
try line_rope.walk(&counters, LineWalker.walk);
total_chunks = counters[0];
total_bytes = counters[1];
try std.testing.expectEqual(@as(u32, 5), total_chunks); // 2 + 3
try std.testing.expectEqual(@as(u32, 5), total_bytes); // All single char chunks
}
test "Nested Rope - simulate full text buffer workflow" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const chunks1 = [_]Chunk{
.{ .data = "Hello ", .width = 6 },
.{ .data = "World", .width = 5 },
};
const line1 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1),
.line_id = 1,
};
var document = try rope_mod.Rope(Line).from_item(allocator, line1);
const chunks2 = [_]Chunk{.{ .data = "Goodbye", .width = 7 }};
const line2 = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2),
.line_id = 2,
};
try document.append(line2);
try std.testing.expectEqual(@as(u32, 2), document.count());
const retrieved_line1 = document.get(0).?;
try std.testing.expectEqual(@as(u32, 2), retrieved_line1.chunks.count());
const retrieved_line2 = document.get(1).?;
try std.testing.expectEqual(@as(u32, 1), retrieved_line2.chunks.count());
var modified_chunks = retrieved_line1.chunks;
try modified_chunks.insert(1, .{ .data = "Beautiful ", .width = 10 });
const modified_line = Line{
.chunks = modified_chunks,
.line_id = 1,
};
// Note: In a real text buffer, you'd replace the line in the document
// For now, just verify the modified line has the new chunk
try std.testing.expectEqual(@as(u32, 3), modified_line.chunks.count());
try std.testing.expectEqualStrings("Beautiful ", modified_line.chunks.get(1).?.data);
}
test "Nested Rope - empty lines with no chunks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const empty_chunks: []const Chunk = &[_]Chunk{};
const empty_line = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, empty_chunks),
.line_id = 1,
};
const chunks = [_]Chunk{.{ .data = "Content", .width = 7 }};
const content_line = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks),
.line_id = 2,
};
const lines = [_]Line{ empty_line, content_line };
var line_rope = try rope_mod.Rope(Line).from_slice(allocator, &lines);
try std.testing.expectEqual(@as(u32, 2), line_rope.count());
const empty = line_rope.get(0).?;
try std.testing.expectEqual(@as(u32, 0), empty.chunks.count());
const content = line_rope.get(1).?;
try std.testing.expectEqual(@as(u32, 1), content.chunks.count());
try std.testing.expectEqualStrings("Content", content.chunks.get(0).?.data);
}
test "Nested Rope - large document with many lines and chunks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var lines_array: [20]Line = undefined;
for (&lines_array, 0..) |*line, line_idx| {
var chunks_array: [3]Chunk = undefined;
for (&chunks_array, 0..) |*chunk, chunk_idx| {
chunk.* = .{
.data = "X",
.width = 1,
};
_ = chunk_idx;
}
line.* = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks_array),
.line_id = @intCast(line_idx),
};
}
var document = try rope_mod.Rope(Line).from_slice(allocator, &lines_array);
try std.testing.expectEqual(@as(u32, 20), document.count());
const line_5 = document.get(5).?;
try std.testing.expectEqual(@as(u32, 5), line_5.line_id);
try std.testing.expectEqual(@as(u32, 3), line_5.chunks.count());
const line_15 = document.get(15).?;
try std.testing.expectEqual(@as(u32, 15), line_15.line_id);
try std.testing.expectEqualStrings("X", line_5.chunks.get(0).?.data);
}
test "Nested Rope - walk_from specific line" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var lines_array: [5]Line = undefined;
for (&lines_array, 0..) |*line, i| {
const chunks = [_]Chunk{.{ .data = "X", .width = 1 }};
line.* = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks),
.line_id = @intCast(i),
};
}
var document = try rope_mod.Rope(Line).from_slice(allocator, &lines_array);
const Context = struct {
count: u32 = 0,
first_id: ?u32 = null,
fn walker(ctx: *anyopaque, line: *const Line, index: u32) rope_mod.Rope(Line).Node.WalkerResult {
_ = index;
const self = @as(*@This(), @ptrCast(@alignCast(ctx)));
if (self.first_id == null) {
self.first_id = line.line_id;
}
self.count += 1;
return .{};
}
};
var ctx = Context{};
try document.walk_from(3, &ctx, Context.walker);
// Should walk lines 3 and 4 (indices 3 and 4)
try std.testing.expectEqual(@as(u32, 2), ctx.count);
try std.testing.expectEqual(@as(u32, 3), ctx.first_id.?);
}
test "Nested Rope - metrics aggregate correctly through all levels" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
// Line 1: 3 chunks, total width 10
const chunks1 = [_]Chunk{
.{ .data = "abc", .width = 3 },
.{ .data = "def", .width = 3 },
.{ .data = "ghij", .width = 4 },
};
// Line 2: 2 chunks, total width 15
const chunks2 = [_]Chunk{
.{ .data = "12345", .width = 5 },
.{ .data = "6789012345", .width = 10 },
};
const lines = [_]Line{
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks1), .line_id = 1 },
.{ .chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks2), .line_id = 2 },
};
var document = try rope_mod.Rope(Line).from_slice(allocator, &lines);
const line1 = document.get(0).?;
const chunk_metrics1 = line1.chunks.root.metrics();
try std.testing.expectEqual(@as(u32, 3), chunk_metrics1.count);
try std.testing.expectEqual(@as(u32, 10), chunk_metrics1.custom.total_width);
try std.testing.expectEqual(@as(u32, 10), chunk_metrics1.custom.total_bytes);
const line2 = document.get(1).?;
const chunk_metrics2 = line2.chunks.root.metrics();
try std.testing.expectEqual(@as(u32, 2), chunk_metrics2.count);
try std.testing.expectEqual(@as(u32, 15), chunk_metrics2.custom.total_width);
try std.testing.expectEqual(@as(u32, 15), chunk_metrics2.custom.total_bytes);
const line1_metrics = line1.measure();
try std.testing.expectEqual(@as(u32, 10), line1_metrics.total_width);
const doc_metrics = document.root.metrics();
try std.testing.expectEqual(@as(u32, 2), doc_metrics.count); // 2 lines
}
test "Nested Rope - O(log n) access to deeply nested data" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var lines_array: [100]Line = undefined;
for (&lines_array, 0..) |*line, line_idx| {
var chunks_array: [5]Chunk = undefined;
for (&chunks_array, 0..) |*chunk, chunk_idx| {
chunk.* = .{
.data = "c",
.width = 1,
};
_ = chunk_idx;
}
line.* = Line{
.chunks = try rope_mod.Rope(Chunk).from_slice(allocator, &chunks_array),
.line_id = @intCast(line_idx),
};
}
var document = try rope_mod.Rope(Line).from_slice(allocator, &lines_array);
// Access line 50, chunk 3 (deep in the tree)
// This tests O(log lines) + O(log chunks) access
const line_50 = document.get(50).?;
try std.testing.expectEqual(@as(u32, 50), line_50.line_id);
const chunk_3 = line_50.chunks.get(3).?;
try std.testing.expectEqualStrings("c", chunk_3.data);
const doc_depth = document.root.depth();
try std.testing.expect(doc_depth < 20); // log2(100) ≈ 7, with some buffer
const line_depth = line_50.chunks.root.depth();
try std.testing.expect(line_depth < 10); // log2(5) ≈ 3, with buffer
}