diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/string_encoding.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 307 |
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); +} |
