aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/config/RepeatableStringMap.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/config/RepeatableStringMap.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/config/RepeatableStringMap.zig')
-rw-r--r--vendor/libghostty-vt/src/config/RepeatableStringMap.zig198
1 files changed, 198 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/config/RepeatableStringMap.zig b/vendor/libghostty-vt/src/config/RepeatableStringMap.zig
new file mode 100644
index 00000000..d5e63433
--- /dev/null
+++ b/vendor/libghostty-vt/src/config/RepeatableStringMap.zig
@@ -0,0 +1,198 @@
+/// RepeatableStringMap is a key/value that can be repeated to accumulate a
+/// string map. This isn't called "StringMap" because I find that sometimes
+/// leads to confusion that it _accepts_ a map such as JSON dict.
+const RepeatableStringMap = @This();
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const formatterpkg = @import("formatter.zig");
+
+const Map = std.ArrayHashMapUnmanaged(
+ [:0]const u8,
+ [:0]const u8,
+ std.array_hash_map.StringContext,
+ true,
+);
+
+// Allocator for the list is the arena for the parent config.
+map: Map = .{},
+
+pub fn parseCLI(
+ self: *RepeatableStringMap,
+ alloc: Allocator,
+ input: ?[]const u8,
+) !void {
+ const value = input orelse return error.ValueRequired;
+
+ // Empty value resets the list. We don't need to free our values because
+ // the allocator used is always an arena.
+ if (value.len == 0) {
+ self.map.clearRetainingCapacity();
+ return;
+ }
+
+ const index = std.mem.indexOfScalar(
+ u8,
+ value,
+ '=',
+ ) orelse return error.ValueRequired;
+
+ const key = std.mem.trim(u8, value[0..index], &std.ascii.whitespace);
+ const val = std.mem.trim(u8, value[index + 1 ..], &std.ascii.whitespace);
+
+ const key_copy = try alloc.dupeZ(u8, key);
+ errdefer alloc.free(key_copy);
+
+ // Empty value removes the key from the map.
+ if (val.len == 0) {
+ _ = self.map.orderedRemove(key_copy);
+ alloc.free(key_copy);
+ return;
+ }
+
+ const val_copy = try alloc.dupeZ(u8, val);
+ errdefer alloc.free(val_copy);
+
+ try self.map.put(alloc, key_copy, val_copy);
+}
+
+/// Deep copy of the struct. Required by Config.
+pub fn clone(
+ self: *const RepeatableStringMap,
+ alloc: Allocator,
+) Allocator.Error!RepeatableStringMap {
+ var map: Map = .{};
+ try map.ensureTotalCapacity(alloc, self.map.count());
+
+ errdefer {
+ var it = map.iterator();
+ while (it.next()) |entry| {
+ alloc.free(entry.key_ptr.*);
+ alloc.free(entry.value_ptr.*);
+ }
+ map.deinit(alloc);
+ }
+
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ const key = try alloc.dupeZ(u8, entry.key_ptr.*);
+ const value = try alloc.dupeZ(u8, entry.value_ptr.*);
+ map.putAssumeCapacity(key, value);
+ }
+
+ return .{ .map = map };
+}
+
+/// The number of items in the map
+pub fn count(self: RepeatableStringMap) usize {
+ return self.map.count();
+}
+
+/// Iterator over the entries in the map.
+pub fn iterator(self: RepeatableStringMap) Map.Iterator {
+ return self.map.iterator();
+}
+
+/// Compare if two of our value are requal. Required by Config.
+pub fn equal(self: RepeatableStringMap, other: RepeatableStringMap) bool {
+ if (self.map.count() != other.map.count()) return false;
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ const value = other.map.get(entry.key_ptr.*) orelse return false;
+ if (!std.mem.eql(u8, entry.value_ptr.*, value)) return false;
+ } else return true;
+}
+
+/// Used by formatter
+pub fn formatEntry(self: RepeatableStringMap, formatter: formatterpkg.EntryFormatter) !void {
+ // If no items, we want to render an empty field.
+ if (self.map.count() == 0) {
+ try formatter.formatEntry(void, {});
+ return;
+ }
+
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ var buf: [256]u8 = undefined;
+ const value = std.fmt.bufPrint(&buf, "{s}={s}", .{ entry.key_ptr.*, entry.value_ptr.* }) catch |err| switch (err) {
+ error.NoSpaceLeft => return error.OutOfMemory,
+ };
+ try formatter.formatEntry([]const u8, value);
+ }
+}
+
+test "RepeatableStringMap: parseCLI" {
+ const testing = std.testing;
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var map: RepeatableStringMap = .{};
+
+ try testing.expectError(error.ValueRequired, map.parseCLI(alloc, "A"));
+
+ try map.parseCLI(alloc, "A=B");
+ try map.parseCLI(alloc, "B=C");
+ try testing.expectEqual(@as(usize, 2), map.count());
+
+ try map.parseCLI(alloc, "");
+ try testing.expectEqual(@as(usize, 0), map.count());
+
+ try map.parseCLI(alloc, "A=B");
+ try testing.expectEqual(@as(usize, 1), map.count());
+ try map.parseCLI(alloc, "A=C");
+ try testing.expectEqual(@as(usize, 1), map.count());
+}
+
+test "RepeatableStringMap: formatConfig empty" {
+ const testing = std.testing;
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var list: RepeatableStringMap = .{};
+ try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = \n", buf.written());
+}
+
+test "RepeatableStringMap: formatConfig single item" {
+ const testing = std.testing;
+
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ var map: RepeatableStringMap = .{};
+ try map.parseCLI(alloc, "A=B");
+ try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.written());
+ }
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ var map: RepeatableStringMap = .{};
+ try map.parseCLI(alloc, " A = B ");
+ try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.written());
+ }
+}
+
+test "RepeatableStringMap: formatConfig multiple items" {
+ const testing = std.testing;
+
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ var list: RepeatableStringMap = .{};
+ try list.parseCLI(alloc, "A=B");
+ try list.parseCLI(alloc, "B = C");
+ try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.written());
+ }
+}