aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/config/io.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/io.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/config/io.zig')
-rw-r--r--vendor/libghostty-vt/src/config/io.zig254
1 files changed, 254 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/config/io.zig b/vendor/libghostty-vt/src/config/io.zig
new file mode 100644
index 00000000..a1e433b6
--- /dev/null
+++ b/vendor/libghostty-vt/src/config/io.zig
@@ -0,0 +1,254 @@
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const ArenaAllocator = std.heap.ArenaAllocator;
+const string = @import("string.zig");
+const formatterpkg = @import("formatter.zig");
+const cli = @import("../cli.zig");
+
+/// ReadableIO is some kind of IO source that is readable.
+///
+/// It can be either a direct string or a filepath. The filepath will
+/// be deferred and read later, so it won't be checked for existence
+/// or readability at configuration time. This allows using a path that
+/// might be produced in an intermediate state.
+pub const ReadableIO = union(enum) {
+ const Self = @This();
+
+ raw: [:0]const u8,
+ path: [:0]const u8,
+
+ pub fn parseCLI(
+ self: *Self,
+ alloc: Allocator,
+ input_: ?[]const u8,
+ ) !void {
+ const input = input_ orelse return error.ValueRequired;
+ if (input.len == 0) return error.ValueRequired;
+
+ // We create a buffer only to do string parsing and validate
+ // it works. We store the value as raw so that our formatting
+ // can recreate it.
+ {
+ const buf = try alloc.alloc(u8, input.len);
+ defer alloc.free(buf);
+ _ = try string.parse(buf, input);
+ }
+
+ // Next, parse the tagged union using normal rules.
+ self.* = cli.args.parseTaggedUnion(
+ Self,
+ alloc,
+ input,
+ ) catch |err| switch (err) {
+ // Invalid values in the tagged union are interpreted as
+ // raw values. This lets users pass in simple string values
+ // without needing to tag them.
+ error.InvalidValue => .{ .raw = try alloc.dupeZ(u8, input) },
+ else => return err,
+ };
+ }
+
+ pub fn clone(self: Self, alloc: Allocator) Allocator.Error!Self {
+ return switch (self) {
+ .raw => |v| .{ .raw = try alloc.dupeZ(u8, v) },
+ .path => |v| .{ .path = try alloc.dupeZ(u8, v) },
+ };
+ }
+
+ /// Same as clone but also parses the values as Zig strings in
+ /// the final resulting value all at once so we can avoid extra
+ /// allocations.
+ pub fn cloneParsed(
+ self: Self,
+ alloc: Allocator,
+ ) Allocator.Error!Self {
+ switch (self) {
+ inline else => |v, tag| {
+ // Parsing can't fail because we validate it in parseCLI
+ const copied = try alloc.dupeZ(u8, v);
+ const parsed = string.parse(copied, v) catch unreachable;
+ assert(copied.ptr == parsed.ptr);
+
+ // If we parsed less than our original length we need
+ // to keep it null-terminated.
+ if (parsed.len < copied.len) copied[parsed.len] = 0;
+
+ return @unionInit(
+ Self,
+ @tagName(tag),
+ copied[0..parsed.len :0],
+ );
+ },
+ }
+ }
+
+ pub fn equal(self: Self, other: Self) bool {
+ if (std.meta.activeTag(self) != std.meta.activeTag(other)) {
+ return false;
+ }
+
+ return switch (self) {
+ .raw => |v| std.mem.eql(u8, v, other.raw),
+ .path => |v| std.mem.eql(u8, v, other.path),
+ };
+ }
+
+ pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void {
+ var buf: [4096]u8 = undefined;
+ var writer: std.Io.Writer = .fixed(&buf);
+ switch (self) {
+ inline else => |v, tag| {
+ writer.writeAll(@tagName(tag)) catch return error.OutOfMemory;
+ writer.writeByte(':') catch return error.OutOfMemory;
+ writer.writeAll(v) catch return error.OutOfMemory;
+ },
+ }
+
+ try formatter.formatEntry(
+ []const u8,
+ writer.buffered(),
+ );
+ }
+
+ test "parseCLI" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+ {
+ var io: Self = undefined;
+ try Self.parseCLI(&io, alloc, "foo");
+ try testing.expect(io == .raw);
+ try testing.expectEqualStrings("foo", io.raw);
+ }
+ {
+ var io: Self = undefined;
+ try Self.parseCLI(&io, alloc, "raw:foo");
+ try testing.expect(io == .raw);
+ try testing.expectEqualStrings("foo", io.raw);
+ }
+ {
+ var io: Self = undefined;
+ try Self.parseCLI(&io, alloc, "path:foo");
+ try testing.expect(io == .path);
+ try testing.expectEqualStrings("foo", io.path);
+ }
+ }
+
+ test "formatEntry" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+
+ var v: Self = undefined;
+ try v.parseCLI(alloc, "raw:foo");
+ try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = raw:foo\n", buf.written());
+ }
+};
+
+pub const RepeatableReadableIO = struct {
+ const Self = @This();
+
+ // Allocator for the list is the arena for the parent config.
+ list: std.ArrayListUnmanaged(ReadableIO) = .{},
+
+ pub fn parseCLI(
+ self: *Self,
+ alloc: Allocator,
+ input: ?[]const u8,
+ ) !void {
+ const value = input orelse return error.ValueRequired;
+
+ // Empty value resets the list
+ if (value.len == 0) {
+ self.list.clearRetainingCapacity();
+ return;
+ }
+
+ var io: ReadableIO = undefined;
+ try ReadableIO.parseCLI(&io, alloc, value);
+ try self.list.append(alloc, io);
+ }
+
+ /// Deep copy of the struct. Required by Config.
+ pub fn clone(self: *const Self, alloc: Allocator) Allocator.Error!Self {
+ var list = try std.ArrayListUnmanaged(ReadableIO).initCapacity(
+ alloc,
+ self.list.items.len,
+ );
+ for (self.list.items) |item| {
+ const copy = try item.clone(alloc);
+ list.appendAssumeCapacity(copy);
+ }
+
+ return .{ .list = list };
+ }
+
+ /// See ReadableIO.cloneParsed
+ pub fn cloneParsed(
+ self: *const Self,
+ alloc: Allocator,
+ ) Allocator.Error!Self {
+ var list = try std.ArrayListUnmanaged(ReadableIO).initCapacity(
+ alloc,
+ self.list.items.len,
+ );
+ for (self.list.items) |item| {
+ const copy = try item.cloneParsed(alloc);
+ list.appendAssumeCapacity(copy);
+ }
+
+ return .{ .list = list };
+ }
+
+ /// Compare if two of our value are requal. Required by Config.
+ pub fn equal(self: Self, other: Self) bool {
+ const itemsA = self.list.items;
+ const itemsB = other.list.items;
+ if (itemsA.len != itemsB.len) return false;
+ for (itemsA, itemsB) |a, b| {
+ if (!a.equal(b)) return false;
+ } else return true;
+ }
+
+ /// Used by Formatter
+ pub fn formatEntry(
+ self: Self,
+ formatter: formatterpkg.EntryFormatter,
+ ) !void {
+ if (self.list.items.len == 0) {
+ try formatter.formatEntry(void, {});
+ return;
+ }
+
+ for (self.list.items) |value| {
+ try formatter.formatEntry(ReadableIO, value);
+ }
+ }
+
+ test "parseCLI" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var list: Self = .{};
+ try list.parseCLI(alloc, "raw:A");
+ try list.parseCLI(alloc, "path:B");
+ try testing.expectEqual(@as(usize, 2), list.list.items.len);
+
+ try list.parseCLI(alloc, "");
+ try testing.expectEqual(@as(usize, 0), list.list.items.len);
+ }
+};
+
+test {
+ _ = ReadableIO;
+ _ = RepeatableReadableIO;
+}