ghostty/src/simd/vt.zig
2024-02-06 08:45:41 -08:00

139 lines
3.2 KiB
Zig

const std = @import("std");
// vt.cpp
extern "c" fn ghostty_simd_decode_utf8_until_control_seq(
input: [*]const u8,
count: usize,
output: [*]u32,
output_count: *usize,
) usize;
const DecodeResult = struct {
consumed: usize,
decoded: usize,
};
pub fn utf8DecodeUntilControlSeq(
input: []const u8,
output: []u32,
) DecodeResult {
var decoded: usize = 0;
const consumed = ghostty_simd_decode_utf8_until_control_seq(
input.ptr,
input.len,
output.ptr,
&decoded,
);
return .{ .consumed = consumed, .decoded = decoded };
}
test "decode no escape" {
const testing = std.testing;
var output: [1024]u32 = undefined;
// TODO: many more test cases
{
const str = "hello" ** 128;
try testing.expectEqual(DecodeResult{
.consumed = str.len,
.decoded = str.len,
}, utf8DecodeUntilControlSeq(str, &output));
}
}
test "decode ASCII to escape" {
const testing = std.testing;
var output: [1024]u32 = undefined;
// TODO: many more test cases
{
const prefix = "hello" ** 64;
const str = prefix ++ "\x1b" ++ ("world" ** 64);
try testing.expectEqual(DecodeResult{
.consumed = prefix.len,
.decoded = prefix.len,
}, utf8DecodeUntilControlSeq(str, &output));
}
}
test "decode immediate esc sequence" {
const testing = std.testing;
var output: [64]u32 = undefined;
const str = "\x1b[?5s";
try testing.expectEqual(DecodeResult{
.consumed = 0,
.decoded = 0,
}, utf8DecodeUntilControlSeq(str, &output));
}
test "decode incomplete UTF-8" {
const testing = std.testing;
var output: [64]u32 = undefined;
// 2-byte
{
const str = "hello\xc2";
try testing.expectEqual(DecodeResult{
.consumed = 5,
.decoded = 5,
}, utf8DecodeUntilControlSeq(str, &output));
}
// 3-byte
{
const str = "hello\xe0\x00";
try testing.expectEqual(DecodeResult{
.consumed = 5,
.decoded = 5,
}, utf8DecodeUntilControlSeq(str, &output));
}
// 4-byte
{
const str = "hello\xf0\x90";
try testing.expectEqual(DecodeResult{
.consumed = 5,
.decoded = 5,
}, utf8DecodeUntilControlSeq(str, &output));
}
}
test "decode invalid UTF-8" {
const testing = std.testing;
var output: [64]u32 = undefined;
// Invalid leading 1s
{
const str = "hello\xc2\x00";
try testing.expectEqual(DecodeResult{
.consumed = 7,
.decoded = 7,
}, utf8DecodeUntilControlSeq(str, &output));
}
try testing.expectEqual(@as(u32, 0xFFFD), output[5]);
}
// This is testing our current behavior so that we know we have to handle
// this case in terminal/stream.zig. If we change this behavior, we can
// remove the special handling in terminal/stream.zig.
test "decode invalid leading byte isn't consumed or replaced" {
const testing = std.testing;
var output: [64]u32 = undefined;
{
const str = "hello\xFF";
try testing.expectEqual(DecodeResult{
.consumed = 5,
.decoded = 5,
}, utf8DecodeUntilControlSeq(str, &output));
}
}