aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os/string_encoding.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/os/string_encoding.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
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 00000000..875a5bc1
--- /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);
+}