aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/datastruct/segmented_list.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/datastruct/segmented_list.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/datastruct/segmented_list.zig')
-rw-r--r--vendor/libghostty-vt/src/datastruct/segmented_list.zig533
1 files changed, 533 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/datastruct/segmented_list.zig b/vendor/libghostty-vt/src/datastruct/segmented_list.zig
new file mode 100644
index 00000000..5c0601c5
--- /dev/null
+++ b/vendor/libghostty-vt/src/datastruct/segmented_list.zig
@@ -0,0 +1,533 @@
+//! Code taken from 0.15.2 `std.segmented_list` (MIT license). See
+//! lib/compat/README.md for license and details.
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const testing = std.testing;
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+
+// Imagine that `fn at(self: *Self, index: usize) &T` is a customer asking for a box
+// from a warehouse, based on a flat array, boxes ordered from 0 to N - 1.
+// But the warehouse actually stores boxes in shelves of increasing powers of 2 sizes.
+// So when the customer requests a box index, we have to translate it to shelf index
+// and box index within that shelf. Illustration:
+//
+// customer indexes:
+// shelf 0: 0
+// shelf 1: 1 2
+// shelf 2: 3 4 5 6
+// shelf 3: 7 8 9 10 11 12 13 14
+// shelf 4: 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
+// shelf 5: 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
+// ...
+//
+// warehouse indexes:
+// shelf 0: 0
+// shelf 1: 0 1
+// shelf 2: 0 1 2 3
+// shelf 3: 0 1 2 3 4 5 6 7
+// shelf 4: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+// shelf 5: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+// ...
+//
+// With this arrangement, here are the equations to get the shelf index and
+// box index based on customer box index:
+//
+// shelf_index = floor(log2(customer_index + 1))
+// shelf_count = ceil(log2(box_count + 1))
+// box_index = customer_index + 1 - 2 ** shelf
+// shelf_size = 2 ** shelf_index
+//
+// Now we complicate it a little bit further by adding a preallocated shelf, which must be
+// a power of 2:
+// prealloc=4
+//
+// customer indexes:
+// prealloc: 0 1 2 3
+// shelf 0: 4 5 6 7 8 9 10 11
+// shelf 1: 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
+// shelf 2: 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
+// ...
+//
+// warehouse indexes:
+// prealloc: 0 1 2 3
+// shelf 0: 0 1 2 3 4 5 6 7
+// shelf 1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+// shelf 2: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+// ...
+//
+// Now the equations are:
+//
+// shelf_index = floor(log2(customer_index + prealloc)) - log2(prealloc) - 1
+// shelf_count = ceil(log2(box_count + prealloc)) - log2(prealloc) - 1
+// box_index = customer_index + prealloc - 2 ** (log2(prealloc) + 1 + shelf)
+// shelf_size = prealloc * 2 ** (shelf_index + 1)
+
+/// This is a stack data structure where pointers to indexes have the same lifetime as the data structure
+/// itself, unlike ArrayList where append() invalidates all existing element pointers.
+/// The tradeoff is that elements are not guaranteed to be contiguous. For that, use ArrayList.
+/// Note however that most elements are contiguous, making this data structure cache-friendly.
+///
+/// Because it never has to copy elements from an old location to a new location, it does not require
+/// its elements to be copyable, and it avoids wasting memory when backed by an ArenaAllocator.
+/// Note that the append() and pop() convenience methods perform a copy, but you can instead use
+/// addOne(), at(), setCapacity(), and shrinkCapacity() to avoid copying items.
+///
+/// This data structure has O(1) append and O(1) pop.
+///
+/// It supports preallocated elements, making it especially well suited when the expected maximum
+/// size is small. `prealloc_item_count` must be 0, or a power of 2.
+pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type {
+ return struct {
+ const Self = @This();
+ const ShelfIndex = std.math.Log2Int(usize);
+
+ const prealloc_exp: ShelfIndex = blk: {
+ // we don't use the prealloc_exp constant when prealloc_item_count is 0
+ // but lazy-init may still be triggered by other code so supply a value
+ if (prealloc_item_count == 0) {
+ break :blk 0;
+ } else {
+ assert(std.math.isPowerOfTwo(prealloc_item_count));
+ const value = std.math.log2_int(usize, prealloc_item_count);
+ break :blk value;
+ }
+ };
+
+ prealloc_segment: [prealloc_item_count]T = undefined,
+ dynamic_segments: [][*]T = &[_][*]T{},
+ len: usize = 0,
+
+ pub const prealloc_count = prealloc_item_count;
+
+ fn AtType(comptime SelfType: type) type {
+ if (@typeInfo(SelfType).pointer.is_const) {
+ return *const T;
+ } else {
+ return *T;
+ }
+ }
+
+ pub fn deinit(self: *Self, allocator: Allocator) void {
+ self.freeShelves(allocator, @as(ShelfIndex, @intCast(self.dynamic_segments.len)), 0);
+ allocator.free(self.dynamic_segments);
+ self.* = undefined;
+ }
+
+ pub fn at(self: anytype, i: usize) AtType(@TypeOf(self)) {
+ assert(i < self.len);
+ return self.uncheckedAt(i);
+ }
+
+ pub fn count(self: Self) usize {
+ return self.len;
+ }
+
+ pub fn append(self: *Self, allocator: Allocator, item: T) Allocator.Error!void {
+ const new_item_ptr = try self.addOne(allocator);
+ new_item_ptr.* = item;
+ }
+
+ pub fn appendSlice(self: *Self, allocator: Allocator, items: []const T) Allocator.Error!void {
+ for (items) |item| {
+ try self.append(allocator, item);
+ }
+ }
+
+ pub fn pop(self: *Self) ?T {
+ if (self.len == 0) return null;
+
+ const index = self.len - 1;
+ const result = uncheckedAt(self, index).*;
+ self.len = index;
+ return result;
+ }
+
+ pub fn addOne(self: *Self, allocator: Allocator) Allocator.Error!*T {
+ const new_length = self.len + 1;
+ try self.growCapacity(allocator, new_length);
+ const result = uncheckedAt(self, self.len);
+ self.len = new_length;
+ return result;
+ }
+
+ /// Reduce length to `new_len`.
+ /// Invalidates pointers for the elements at index new_len and beyond.
+ pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
+ assert(new_len <= self.len);
+ self.len = new_len;
+ }
+
+ /// Invalidates all element pointers.
+ pub fn clearRetainingCapacity(self: *Self) void {
+ self.len = 0;
+ }
+
+ /// Invalidates all element pointers.
+ pub fn clearAndFree(self: *Self, allocator: Allocator) void {
+ self.setCapacity(allocator, 0) catch unreachable;
+ self.len = 0;
+ }
+
+ /// Grows or shrinks capacity to match usage.
+ /// TODO update this and related methods to match the conventions set by ArrayList
+ pub fn setCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
+ if (prealloc_item_count != 0) {
+ if (new_capacity <= @as(usize, 1) << (prealloc_exp + @as(ShelfIndex, @intCast(self.dynamic_segments.len)))) {
+ return self.shrinkCapacity(allocator, new_capacity);
+ }
+ }
+ return self.growCapacity(allocator, new_capacity);
+ }
+
+ /// Only grows capacity, or retains current capacity.
+ pub fn growCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
+ const new_cap_shelf_count = shelfCount(new_capacity);
+ const old_shelf_count = @as(ShelfIndex, @intCast(self.dynamic_segments.len));
+ if (new_cap_shelf_count <= old_shelf_count) return;
+
+ const new_dynamic_segments = try allocator.alloc([*]T, new_cap_shelf_count);
+ errdefer allocator.free(new_dynamic_segments);
+
+ var i: ShelfIndex = 0;
+ while (i < old_shelf_count) : (i += 1) {
+ new_dynamic_segments[i] = self.dynamic_segments[i];
+ }
+ errdefer while (i > old_shelf_count) : (i -= 1) {
+ allocator.free(new_dynamic_segments[i][0..shelfSize(i)]);
+ };
+ while (i < new_cap_shelf_count) : (i += 1) {
+ new_dynamic_segments[i] = (try allocator.alloc(T, shelfSize(i))).ptr;
+ }
+
+ allocator.free(self.dynamic_segments);
+ self.dynamic_segments = new_dynamic_segments;
+ }
+
+ /// Only shrinks capacity or retains current capacity.
+ /// It may fail to reduce the capacity in which case the capacity will remain unchanged.
+ pub fn shrinkCapacity(self: *Self, allocator: Allocator, new_capacity: usize) void {
+ if (new_capacity <= prealloc_item_count) {
+ const len = @as(ShelfIndex, @intCast(self.dynamic_segments.len));
+ self.freeShelves(allocator, len, 0);
+ allocator.free(self.dynamic_segments);
+ self.dynamic_segments = &[_][*]T{};
+ return;
+ }
+
+ const new_cap_shelf_count = shelfCount(new_capacity);
+ const old_shelf_count = @as(ShelfIndex, @intCast(self.dynamic_segments.len));
+ assert(new_cap_shelf_count <= old_shelf_count);
+ if (new_cap_shelf_count == old_shelf_count) return;
+
+ // freeShelves() must be called before resizing the dynamic
+ // segments, but we don't know if resizing the dynamic segments
+ // will work until we try it. So we must allocate a fresh memory
+ // buffer in order to reduce capacity.
+ const new_dynamic_segments = allocator.alloc([*]T, new_cap_shelf_count) catch return;
+ self.freeShelves(allocator, old_shelf_count, new_cap_shelf_count);
+ if (allocator.resize(self.dynamic_segments, new_cap_shelf_count)) {
+ // We didn't need the new memory allocation after all.
+ self.dynamic_segments = self.dynamic_segments[0..new_cap_shelf_count];
+ allocator.free(new_dynamic_segments);
+ } else {
+ // Good thing we allocated that new memory slice.
+ @memcpy(new_dynamic_segments, self.dynamic_segments[0..new_cap_shelf_count]);
+ allocator.free(self.dynamic_segments);
+ self.dynamic_segments = new_dynamic_segments;
+ }
+ }
+
+ pub fn shrink(self: *Self, new_len: usize) void {
+ assert(new_len <= self.len);
+ // TODO take advantage of the new realloc semantics
+ self.len = new_len;
+ }
+
+ pub fn writeToSlice(self: *Self, dest: []T, start: usize) void {
+ const end = start + dest.len;
+ assert(end <= self.len);
+
+ var i = start;
+ if (end <= prealloc_item_count) {
+ const src = self.prealloc_segment[i..end];
+ @memcpy(dest[i - start ..][0..src.len], src);
+ return;
+ } else if (i < prealloc_item_count) {
+ const src = self.prealloc_segment[i..];
+ @memcpy(dest[i - start ..][0..src.len], src);
+ i = prealloc_item_count;
+ }
+
+ while (i < end) {
+ const shelf_index = shelfIndex(i);
+ const copy_start = boxIndex(i, shelf_index);
+ const copy_end = @min(shelfSize(shelf_index), copy_start + end - i);
+ const src = self.dynamic_segments[shelf_index][copy_start..copy_end];
+ @memcpy(dest[i - start ..][0..src.len], src);
+ i += (copy_end - copy_start);
+ }
+ }
+
+ pub fn uncheckedAt(self: anytype, index: usize) AtType(@TypeOf(self)) {
+ if (index < prealloc_item_count) {
+ return &self.prealloc_segment[index];
+ }
+ const shelf_index = shelfIndex(index);
+ const box_index = boxIndex(index, shelf_index);
+ return &self.dynamic_segments[shelf_index][box_index];
+ }
+
+ fn shelfCount(box_count: usize) ShelfIndex {
+ if (prealloc_item_count == 0) {
+ return log2_int_ceil(usize, box_count + 1);
+ }
+ return log2_int_ceil(usize, box_count + prealloc_item_count) - prealloc_exp - 1;
+ }
+
+ fn shelfSize(shelf_index: ShelfIndex) usize {
+ if (prealloc_item_count == 0) {
+ return @as(usize, 1) << shelf_index;
+ }
+ return @as(usize, 1) << (shelf_index + (prealloc_exp + 1));
+ }
+
+ fn shelfIndex(list_index: usize) ShelfIndex {
+ if (prealloc_item_count == 0) {
+ return std.math.log2_int(usize, list_index + 1);
+ }
+ return std.math.log2_int(usize, list_index + prealloc_item_count) - prealloc_exp - 1;
+ }
+
+ fn boxIndex(list_index: usize, shelf_index: ShelfIndex) usize {
+ if (prealloc_item_count == 0) {
+ return (list_index + 1) - (@as(usize, 1) << shelf_index);
+ }
+ return list_index + prealloc_item_count - (@as(usize, 1) << ((prealloc_exp + 1) + shelf_index));
+ }
+
+ fn freeShelves(self: *Self, allocator: Allocator, from_count: ShelfIndex, to_count: ShelfIndex) void {
+ var i = from_count;
+ while (i != to_count) {
+ i -= 1;
+ allocator.free(self.dynamic_segments[i][0..shelfSize(i)]);
+ }
+ }
+
+ pub const Iterator = BaseIterator(*Self, *T);
+ pub const ConstIterator = BaseIterator(*const Self, *const T);
+ fn BaseIterator(comptime SelfType: type, comptime ElementPtr: type) type {
+ return struct {
+ list: SelfType,
+ index: usize,
+ box_index: usize,
+ shelf_index: ShelfIndex,
+ shelf_size: usize,
+
+ pub fn next(it: *@This()) ?ElementPtr {
+ if (it.index >= it.list.len) return null;
+ if (it.index < prealloc_item_count) {
+ const ptr = &it.list.prealloc_segment[it.index];
+ it.index += 1;
+ if (it.index == prealloc_item_count) {
+ it.box_index = 0;
+ it.shelf_index = 0;
+ it.shelf_size = prealloc_item_count * 2;
+ }
+ return ptr;
+ }
+
+ const ptr = &it.list.dynamic_segments[it.shelf_index][it.box_index];
+ it.index += 1;
+ it.box_index += 1;
+ if (it.box_index == it.shelf_size) {
+ it.shelf_index += 1;
+ it.box_index = 0;
+ it.shelf_size *= 2;
+ }
+ return ptr;
+ }
+
+ pub fn prev(it: *@This()) ?ElementPtr {
+ if (it.index == 0) return null;
+
+ it.index -= 1;
+ if (it.index < prealloc_item_count) return &it.list.prealloc_segment[it.index];
+
+ if (it.box_index == 0) {
+ it.shelf_index -= 1;
+ it.shelf_size /= 2;
+ it.box_index = it.shelf_size - 1;
+ } else {
+ it.box_index -= 1;
+ }
+
+ return &it.list.dynamic_segments[it.shelf_index][it.box_index];
+ }
+
+ pub fn peek(it: *@This()) ?ElementPtr {
+ if (it.index >= it.list.len)
+ return null;
+ if (it.index < prealloc_item_count)
+ return &it.list.prealloc_segment[it.index];
+
+ return &it.list.dynamic_segments[it.shelf_index][it.box_index];
+ }
+
+ pub fn set(it: *@This(), index: usize) void {
+ it.index = index;
+ if (index < prealloc_item_count) return;
+ it.shelf_index = shelfIndex(index);
+ it.box_index = boxIndex(index, it.shelf_index);
+ it.shelf_size = shelfSize(it.shelf_index);
+ }
+ };
+ }
+
+ pub fn iterator(self: *Self, start_index: usize) Iterator {
+ var it = Iterator{
+ .list = self,
+ .index = undefined,
+ .shelf_index = undefined,
+ .box_index = undefined,
+ .shelf_size = undefined,
+ };
+ it.set(start_index);
+ return it;
+ }
+
+ pub fn constIterator(self: *const Self, start_index: usize) ConstIterator {
+ var it = ConstIterator{
+ .list = self,
+ .index = undefined,
+ .shelf_index = undefined,
+ .box_index = undefined,
+ .shelf_size = undefined,
+ };
+ it.set(start_index);
+ return it;
+ }
+ };
+}
+
+test "basic usage" {
+ try testSegmentedList(0);
+ try testSegmentedList(1);
+ try testSegmentedList(2);
+ try testSegmentedList(4);
+ try testSegmentedList(8);
+ try testSegmentedList(16);
+}
+
+fn testSegmentedList(comptime prealloc: usize) !void {
+ var list = SegmentedList(i32, prealloc){};
+ defer list.deinit(testing.allocator);
+
+ {
+ var i: usize = 0;
+ while (i < 100) : (i += 1) {
+ try list.append(testing.allocator, @as(i32, @intCast(i + 1)));
+ try testing.expect(list.len == i + 1);
+ }
+ }
+
+ {
+ var i: usize = 0;
+ while (i < 100) : (i += 1) {
+ try testing.expect(list.at(i).* == @as(i32, @intCast(i + 1)));
+ }
+ }
+
+ {
+ var it = list.iterator(0);
+ var x: i32 = 0;
+ while (it.next()) |item| {
+ x += 1;
+ try testing.expect(item.* == x);
+ }
+ try testing.expect(x == 100);
+ while (it.prev()) |item| : (x -= 1) {
+ try testing.expect(item.* == x);
+ }
+ try testing.expect(x == 0);
+ }
+
+ {
+ var it = list.constIterator(0);
+ var x: i32 = 0;
+ while (it.next()) |item| {
+ x += 1;
+ try testing.expect(item.* == x);
+ }
+ try testing.expect(x == 100);
+ while (it.prev()) |item| : (x -= 1) {
+ try testing.expect(item.* == x);
+ }
+ try testing.expect(x == 0);
+ }
+
+ try testing.expect(list.pop().? == 100);
+ try testing.expect(list.len == 99);
+
+ try list.appendSlice(testing.allocator, &[_]i32{ 1, 2, 3 });
+ try testing.expect(list.len == 102);
+ try testing.expect(list.pop().? == 3);
+ try testing.expect(list.pop().? == 2);
+ try testing.expect(list.pop().? == 1);
+ try testing.expect(list.len == 99);
+
+ try list.appendSlice(testing.allocator, &[_]i32{});
+ try testing.expect(list.len == 99);
+
+ {
+ var i: i32 = 99;
+ while (list.pop()) |item| : (i -= 1) {
+ try testing.expect(item == i);
+ list.shrinkCapacity(testing.allocator, list.len);
+ }
+ }
+
+ {
+ var control: [100]i32 = undefined;
+ var dest: [100]i32 = undefined;
+
+ var i: i32 = 0;
+ while (i < 100) : (i += 1) {
+ try list.append(testing.allocator, i + 1);
+ control[@as(usize, @intCast(i))] = i + 1;
+ }
+
+ @memset(dest[0..], 0);
+ list.writeToSlice(dest[0..], 0);
+ try testing.expect(mem.eql(i32, control[0..], dest[0..]));
+
+ @memset(dest[0..], 0);
+ list.writeToSlice(dest[50..], 50);
+ try testing.expect(mem.eql(i32, control[50..], dest[50..]));
+ }
+
+ try list.setCapacity(testing.allocator, 0);
+}
+
+test "clearRetainingCapacity" {
+ var list = SegmentedList(i32, 1){};
+ defer list.deinit(testing.allocator);
+
+ try list.appendSlice(testing.allocator, &[_]i32{ 4, 5 });
+ list.clearRetainingCapacity();
+ try list.append(testing.allocator, 6);
+ try testing.expect(list.at(0).* == 6);
+ try testing.expect(list.len == 1);
+ list.clearRetainingCapacity();
+ try testing.expect(list.len == 0);
+}
+
+/// TODO look into why this std.math function was changed in
+/// fc9430f56798a53f9393a697f4ccd6bf9981b970.
+fn log2_int_ceil(comptime T: type, x: T) std.math.Log2Int(T) {
+ assert(x != 0);
+ const log2_val = std.math.log2_int(T, x);
+ if (@as(T, 1) << log2_val == x)
+ return log2_val;
+ return log2_val + 1;
+}