aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/os/string_encoding.zig
diff options
context:
space:
mode:
authorsl0p <sl0p@sl0p.foo>2026-08-13 22:00:03 +0200
committersl0p <sl0p@sl0p.foo>2026-08-13 22:00:03 +0200
commit2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8 (patch)
tree7cd6274303ba1da1f8bd57371aec336dd687fb58 /vendor/libghostty-vt/src/os/string_encoding.zig
downloadslosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.gz
slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.xz
slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.zip
sl0ptty: M0 — libghostty-vt vendored, one pane, compositor, diff renderer
Design contract in DESIGN.md. libghostty-vt vendored fresh from ghostty main@bedb6f2 (zig 0.16), built to a 14MB static lib. M0: pty spawn (no libutil), a lib-vt terminal per pane, a screen cell buffer, a diff emitter that repaints only what changed, raw mode, SIGWINCH, and a ~120Hz frame cap that coalesces pty reads. Also the seed of the headless harness: --headless dumps the composited screen without a tty, so composition is testable in milliseconds. tests/live_m0.py covers what headless cannot: raw mode, the diff emitter, resize propagation, and the quit path. 6/6 green.
Diffstat (limited to 'vendor/libghostty-vt/src/os/string_encoding.zig')
-rw-r--r--vendor/libghostty-vt/src/os/string_encoding.zig307
1 files changed, 307 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/os/string_encoding.zig b/vendor/libghostty-vt/src/os/string_encoding.zig
new file mode 100644
index 0000000..875a5bc
--- /dev/null
+++ b/vendor/libghostty-vt/src/os/string_encoding.zig
@@ -0,0 +1,307 @@
+const std = @import("std");
+
+/// Decode date from the buffer that has been encoded in the same way that
+/// `bash`'s `printf %q` encodes a string and write it to the writer. If an
+/// error is returned garbage may have been written to the buffer.
+pub fn printfQDecode(writer: *std.Io.Writer, buf: []const u8) (std.Io.Writer.Error || error{DecodeError})!void {
+ const data: []const u8 = data: {
+ // Strip off `$''` quoting.
+ if (std.mem.startsWith(u8, buf, "$'")) {
+ if (buf.len < 3 or !std.mem.endsWith(u8, buf, "'")) return error.DecodeError;
+ break :data buf[2 .. buf.len - 1];
+ }
+ // Strip off `''` quoting.
+ if (std.mem.startsWith(u8, buf, "'")) {
+ if (buf.len < 2 or !std.mem.endsWith(u8, buf, "'")) return error.DecodeError;
+ break :data buf[1 .. buf.len - 1];
+ }
+ break :data buf;
+ };
+
+ var src: usize = 0;
+
+ while (src < data.len) {
+ switch (data[src]) {
+ else => {
+ try writer.writeByte(data[src]);
+ src += 1;
+ },
+ '\\' => {
+ if (src + 1 >= data.len) return error.DecodeError;
+ switch (data[src + 1]) {
+ ' ',
+ '\\',
+ '"',
+ '\'',
+ '$',
+ => |c| {
+ try writer.writeByte(c);
+ src += 2;
+ },
+ 'e' => {
+ try writer.writeByte(std.ascii.control_code.esc);
+ src += 2;
+ },
+ 'n' => {
+ try writer.writeByte(std.ascii.control_code.lf);
+ src += 2;
+ },
+ 'r' => {
+ try writer.writeByte(std.ascii.control_code.cr);
+ src += 2;
+ },
+ 't' => {
+ try writer.writeByte(std.ascii.control_code.ht);
+ src += 2;
+ },
+ 'v' => {
+ try writer.writeByte(std.ascii.control_code.vt);
+ src += 2;
+ },
+ else => return error.DecodeError,
+ }
+ },
+ }
+ }
+}
+
+test "printf_q 1" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr\\ kurwa";
+
+ try printfQDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr kurwa", w.written());
+}
+
+test "printf_q 2" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "bobr\\nkurwa";
+
+ try printfQDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr\nkurwa", w.written());
+}
+
+test "printf_q 3" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "bobr\\dkurwa";
+
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+test "printf_q 4" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "bobr kurwa\\";
+
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+test "printf_q 5" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "$'bobr kurwa'";
+
+ try printfQDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr kurwa", w.written());
+}
+
+test "printf_q 6" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "'bobr kurwa'";
+
+ try printfQDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr kurwa", w.written());
+}
+
+test "printf_q 7" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "$'bobr kurwa";
+
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+test "printf_q 8" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+ const s: [:0]const u8 = "$'";
+ var src: [s.len:0]u8 = undefined;
+ @memcpy(&src, s);
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+test "printf_q 9" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+ const s: [:0]const u8 = "'bobr kurwa";
+ var src: [s.len:0]u8 = undefined;
+ @memcpy(&src, s);
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+test "printf_q 10" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: [:0]const u8 = "'";
+ var src: [s.len:0]u8 = undefined;
+ @memcpy(&src, s);
+ try std.testing.expectError(error.DecodeError, printfQDecode(&w.writer, s));
+}
+
+/// Decode data from the buffer that has been URL percent encoded and write
+/// it to the given buffer. If an error is returned the garbage may have been
+/// written to the writer.
+pub fn urlPercentDecode(writer: *std.Io.Writer, buf: []const u8) (std.Io.Writer.Error || error{DecodeError})!void {
+ var src: usize = 0;
+ while (src < buf.len) {
+ switch (buf[src]) {
+ else => {
+ try writer.writeByte(buf[src]);
+ src += 1;
+ },
+ '%' => {
+ if (src + 2 >= buf.len) return error.DecodeError;
+ switch (buf[src + 1]) {
+ '0'...'9', 'a'...'f', 'A'...'F' => {
+ switch (buf[src + 2]) {
+ '0'...'9', 'a'...'f', 'A'...'F' => {
+ try writer.writeByte(std.math.shl(u8, hex(buf[src + 1]), 4) | hex(buf[src + 2]));
+ src += 3;
+ },
+ else => return error.DecodeError,
+ }
+ },
+ else => return error.DecodeError,
+ }
+ },
+ }
+ }
+}
+
+inline fn hex(c: u8) u4 {
+ switch (c) {
+ '0'...'9' => return @truncate(c - '0'),
+ 'a'...'f' => return @truncate(c - 'a' + 10),
+ 'A'...'F' => return @truncate(c - 'A' + 10),
+ else => unreachable,
+ }
+}
+
+test "singles percent" {
+ for (0..255) |c| {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ var buf_: [4]u8 = undefined;
+ const buf = try std.fmt.bufPrintZ(&buf_, "%{x:0>2}", .{c});
+
+ try urlPercentDecode(&w.writer, buf);
+ const decoded = w.written();
+
+ try std.testing.expectEqual(1, decoded.len);
+ try std.testing.expectEqual(c, decoded[0]);
+ }
+ for (0..255) |c| {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ var buf_: [4]u8 = undefined;
+ const buf = try std.fmt.bufPrintZ(&buf_, "%{X:0>2}", .{c});
+
+ try urlPercentDecode(&w.writer, buf);
+ const decoded = w.written();
+
+ try std.testing.expectEqual(1, decoded.len);
+ try std.testing.expectEqual(c, decoded[0]);
+ }
+}
+
+test "percent 1" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%20kurwa";
+
+ try urlPercentDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr kurwa", w.written());
+}
+
+test "percent 2" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%2kurwa";
+
+ try std.testing.expectError(error.DecodeError, urlPercentDecode(&w.writer, s));
+}
+
+test "percent 3" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%kurwa";
+
+ try std.testing.expectError(error.DecodeError, urlPercentDecode(&w.writer, s));
+}
+
+test "percent 4" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%%kurwa";
+
+ try std.testing.expectError(error.DecodeError, urlPercentDecode(&w.writer, s));
+}
+
+test "percent 5" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%20kurwa%20";
+
+ try urlPercentDecode(&w.writer, s);
+ try std.testing.expectEqualStrings("bobr kurwa ", w.written());
+}
+
+test "percent 6" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%20kurwa%2";
+
+ try std.testing.expectError(error.DecodeError, urlPercentDecode(&w.writer, s));
+}
+
+test "percent 7" {
+ var w: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer w.deinit();
+
+ const s: []const u8 = "bobr%20kurwa%";
+
+ try std.testing.expectError(error.DecodeError, urlPercentDecode(&w.writer, s));
+}
+
+/// Is the given character valid in URI percent encoding?
+fn isValidChar(c: u8) bool {
+ return switch (c) {
+ ' ', ';', '=' => false,
+ else => return std.ascii.isPrint(c),
+ };
+}
+
+/// Write data to the writer after URI percent encoding.
+pub fn urlPercentEncode(writer: *std.Io.Writer, data: []const u8) std.Io.Writer.Error!void {
+ try std.Uri.Component.percentEncode(writer, data, isValidChar);
+}