aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/simd
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/simd
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/simd')
-rw-r--r--vendor/libghostty-vt/src/simd/base64.cpp20
-rw-r--r--vendor/libghostty-vt/src/simd/base64.zig87
-rw-r--r--vendor/libghostty-vt/src/simd/base64_scalar.zig147
-rw-r--r--vendor/libghostty-vt/src/simd/codepoint_width.cpp488
-rw-r--r--vendor/libghostty-vt/src/simd/codepoint_width.zig52
-rw-r--r--vendor/libghostty-vt/src/simd/index_of.cpp53
-rw-r--r--vendor/libghostty-vt/src/simd/index_of.h98
-rw-r--r--vendor/libghostty-vt/src/simd/index_of.zig35
-rw-r--r--vendor/libghostty-vt/src/simd/main.zig14
-rw-r--r--vendor/libghostty-vt/src/simd/vt.cpp169
-rw-r--r--vendor/libghostty-vt/src/simd/vt.h30
-rw-r--r--vendor/libghostty-vt/src/simd/vt.zig196
12 files changed, 1389 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/simd/base64.cpp b/vendor/libghostty-vt/src/simd/base64.cpp
new file mode 100644
index 00000000..63f26b50
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/base64.cpp
@@ -0,0 +1,20 @@
+#include <simdutf.h>
+
+extern "C" {
+
+size_t ghostty_simd_base64_max_length(const char* input, size_t length) {
+ return simdutf::maximal_binary_length_from_base64(input, length);
+}
+
+size_t ghostty_simd_base64_decode(const char* input,
+ size_t length,
+ char* output) {
+ simdutf::result r = simdutf::base64_to_binary(input, length, output);
+ if (r.error) {
+ return -1;
+ }
+
+ return r.count;
+}
+
+} // extern "C"
diff --git a/vendor/libghostty-vt/src/simd/base64.zig b/vendor/libghostty-vt/src/simd/base64.zig
new file mode 100644
index 00000000..81feeb72
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/base64.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const options = @import("build_options");
+const assert = @import("../quirks.zig").inlineAssert;
+const scalar_decoder = @import("base64_scalar.zig").scalar_decoder;
+
+const log = std.log.scoped(.simd_base64);
+
+pub fn maxLen(input: []const u8) usize {
+ if (comptime options.simd) return ghostty_simd_base64_max_length(
+ input.ptr,
+ input.len,
+ );
+
+ return maxLenScalar(input);
+}
+
+fn maxLenScalar(input: []const u8) usize {
+ return scalar_decoder.calcSizeForSlice(scalarInput(input)) catch |err| {
+ log.warn("failed to calculate base64 size for payload: {}", .{err});
+ return 0;
+ };
+}
+
+pub fn decode(input: []const u8, output: []u8) error{Base64Invalid}![]const u8 {
+ if (comptime options.simd) {
+ const res = ghostty_simd_base64_decode(
+ input.ptr,
+ input.len,
+ output.ptr,
+ );
+ if (res < 0) return error.Base64Invalid;
+ return output[0..@intCast(res)];
+ }
+
+ return decodeScalar(input, output);
+}
+
+fn decodeScalar(
+ input_raw: []const u8,
+ output: []u8,
+) error{Base64Invalid}![]const u8 {
+ const input = scalarInput(input_raw);
+ const size = maxLenScalar(input);
+ if (size == 0) return "";
+ assert(output.len >= size);
+ scalar_decoder.decode(
+ output,
+ scalarInput(input),
+ ) catch return error.Base64Invalid;
+ return output[0..size];
+}
+
+/// For non-SIMD enabled builds, we trim the padding from the end of the
+/// base64 input in order to get identical output with the SIMD version.
+fn scalarInput(input: []const u8) []const u8 {
+ var i: usize = 0;
+ while (input[input.len - i - 1] == '=') i += 1;
+ return input[0 .. input.len - i];
+}
+
+// base64.cpp
+extern "c" fn ghostty_simd_base64_max_length(
+ input: [*]const u8,
+ len: usize,
+) usize;
+extern "c" fn ghostty_simd_base64_decode(
+ input: [*]const u8,
+ len: usize,
+ output: [*]u8,
+) isize;
+
+test "base64 maxLen" {
+ const testing = std.testing;
+ const len = maxLen("aGVsbG8gd29ybGQ=");
+ try testing.expectEqual(11, len);
+}
+
+test "base64 decode" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const input = "aGVsbG8gd29ybGQ=";
+ const len = maxLen(input);
+ const output = try alloc.alloc(u8, len);
+ defer alloc.free(output);
+ const str = try decode(input, output);
+ try testing.expectEqualStrings("hello world", str);
+}
diff --git a/vendor/libghostty-vt/src/simd/base64_scalar.zig b/vendor/libghostty-vt/src/simd/base64_scalar.zig
new file mode 100644
index 00000000..08886f18
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/base64_scalar.zig
@@ -0,0 +1,147 @@
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+
+pub const scalar_decoder: Base64Decoder = .init(
+ std.base64.standard_alphabet_chars,
+ null,
+);
+
+/// Copied from Zig 0.14.1 stdlib and commented out the invalid padding
+/// scenarios, because Kitty Graphics requires a decoder that doesn't care
+/// about invalid padding scenarios.
+const Base64Decoder = struct {
+ const invalid_char: u8 = 0xff;
+ const invalid_char_tst: u32 = 0xff000000;
+
+ const Error = error{
+ InvalidCharacter,
+ InvalidPadding,
+ NoSpaceLeft,
+ };
+
+ /// e.g. 'A' => 0.
+ /// `invalid_char` for any value not in the 64 alphabet chars.
+ char_to_index: [256]u8,
+ fast_char_to_index: [4][256]u32,
+ pad_char: ?u8,
+
+ pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder {
+ var result = Base64Decoder{
+ .char_to_index = [_]u8{invalid_char} ** 256,
+ .fast_char_to_index = .{[_]u32{invalid_char_tst} ** 256} ** 4,
+ .pad_char = pad_char,
+ };
+
+ var char_in_alphabet = [_]bool{false} ** 256;
+ for (alphabet_chars, 0..) |c, i| {
+ assert(!char_in_alphabet[c]);
+ assert(pad_char == null or c != pad_char.?);
+
+ const ci = @as(u32, @intCast(i));
+ result.fast_char_to_index[0][c] = ci << 2;
+ result.fast_char_to_index[1][c] = (ci >> 4) | ((ci & 0x0f) << 12);
+ result.fast_char_to_index[2][c] = ((ci & 0x3) << 22) | ((ci & 0x3c) << 6);
+ result.fast_char_to_index[3][c] = ci << 16;
+
+ result.char_to_index[c] = @as(u8, @intCast(i));
+ char_in_alphabet[c] = true;
+ }
+ return result;
+ }
+
+ /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding.
+ /// `InvalidPadding` is returned if the input length is not valid.
+ pub fn calcSizeUpperBound(decoder: *const Base64Decoder, source_len: usize) Error!usize {
+ var result = source_len / 4 * 3;
+ const leftover = source_len % 4;
+ if (decoder.pad_char != null) {
+ if (leftover % 4 != 0) return error.InvalidPadding;
+ } else {
+ if (leftover % 4 == 1) return error.InvalidPadding;
+ result += leftover * 3 / 4;
+ }
+ return result;
+ }
+
+ /// Return the exact decoded size for a slice.
+ /// `InvalidPadding` is returned if the input length is not valid.
+ pub fn calcSizeForSlice(decoder: *const Base64Decoder, source: []const u8) Error!usize {
+ const source_len = source.len;
+ var result = try decoder.calcSizeUpperBound(source_len);
+ if (decoder.pad_char) |pad_char| {
+ if (source_len >= 1 and source[source_len - 1] == pad_char) result -= 1;
+ if (source_len >= 2 and source[source_len - 2] == pad_char) result -= 1;
+ }
+ return result;
+ }
+
+ /// dest.len must be what you get from ::calcSize.
+ /// Invalid characters result in `error.InvalidCharacter`.
+ /// Invalid padding results in `error.InvalidPadding`.
+ pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void {
+ if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding;
+ var dest_idx: usize = 0;
+ var fast_src_idx: usize = 0;
+ var acc: u12 = 0;
+ var acc_len: u4 = 0;
+ var leftover_idx: ?usize = null;
+ while (fast_src_idx + 16 < source.len and dest_idx + 15 < dest.len) : ({
+ fast_src_idx += 16;
+ dest_idx += 12;
+ }) {
+ var bits: u128 = 0;
+ inline for (0..4) |i| {
+ var new_bits: u128 = decoder.fast_char_to_index[0][source[fast_src_idx + i * 4]];
+ new_bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1 + i * 4]];
+ new_bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2 + i * 4]];
+ new_bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3 + i * 4]];
+ if ((new_bits & invalid_char_tst) != 0) return error.InvalidCharacter;
+ bits |= (new_bits << (24 * i));
+ }
+ std.mem.writeInt(u128, dest[dest_idx..][0..16], bits, .little);
+ }
+ while (fast_src_idx + 4 < source.len and dest_idx + 3 < dest.len) : ({
+ fast_src_idx += 4;
+ dest_idx += 3;
+ }) {
+ var bits = decoder.fast_char_to_index[0][source[fast_src_idx]];
+ bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1]];
+ bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2]];
+ bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3]];
+ if ((bits & invalid_char_tst) != 0) return error.InvalidCharacter;
+ std.mem.writeInt(u32, dest[dest_idx..][0..4], bits, .little);
+ }
+ const remaining = source[fast_src_idx..];
+ for (remaining, fast_src_idx..) |c, src_idx| {
+ const d = decoder.char_to_index[c];
+ if (d == invalid_char) {
+ if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter;
+ leftover_idx = src_idx;
+ break;
+ }
+ acc = (acc << 6) + d;
+ acc_len += 6;
+ if (acc_len >= 8) {
+ acc_len -= 8;
+ dest[dest_idx] = @as(u8, @truncate(acc >> acc_len));
+ dest_idx += 1;
+ }
+ }
+ // if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) {
+ // return error.InvalidPadding;
+ // }
+ if (leftover_idx == null) return;
+ const leftover = source[leftover_idx.?..];
+ if (decoder.pad_char) |pad_char| {
+ const padding_len = acc_len / 2;
+ var padding_chars: usize = 0;
+ for (leftover) |c| {
+ if (c != pad_char) {
+ return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding;
+ }
+ padding_chars += 1;
+ }
+ if (padding_chars != padding_len) return error.InvalidPadding;
+ }
+ }
+};
diff --git a/vendor/libghostty-vt/src/simd/codepoint_width.cpp b/vendor/libghostty-vt/src/simd/codepoint_width.cpp
new file mode 100644
index 00000000..294922c6
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/codepoint_width.cpp
@@ -0,0 +1,488 @@
+// Generates code for every target that this compiler can support.
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "simd/codepoint_width.cpp" // this file
+#include <hwy/foreach_target.h> // must come before highway.h
+#include <hwy/highway.h>
+#include <hwy/print-inl.h>
+
+#include <cassert>
+#include <iterator>
+
+HWY_BEFORE_NAMESPACE();
+namespace ghostty {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+// East Asian Width
+HWY_ALIGN constexpr uint32_t eaw_gte32[] = {
+ 0x16fe0, 0x16ff0, 0x17000, 0x18800, 0x18d00, 0x1aff0, 0x1aff5, 0x1affd,
+ 0x1b000, 0x1b132, 0x1b150, 0x1b155, 0x1b164, 0x1b170, 0x1f004, 0x1f0cf,
+ 0x1f18e, 0x1f191, 0x1f200, 0x1f210, 0x1f240, 0x1f250, 0x1f260, 0x1f300,
+ 0x1f32d, 0x1f337, 0x1f37e, 0x1f3a0, 0x1f3cf, 0x1f3e0, 0x1f3f4, 0x1f3f8,
+ 0x1f3fb, 0x1f400, 0x1f440, 0x1f442, 0x1f4ff, 0x1f54b, 0x1f550, 0x1f57a,
+ 0x1f595, 0x1f5a4, 0x1f5fb, 0x1f680, 0x1f6cc, 0x1f6d0, 0x1f6d5, 0x1f6dc,
+ 0x1f6eb, 0x1f6f4, 0x1f7e0, 0x1f7f0, 0x1f90c, 0x1f93c, 0x1f947, 0x1fa70,
+ 0x1fa80, 0x1fa90, 0x1fabf, 0x1face, 0x1fae0, 0x1faf0, 0x20000, 0x2a700,
+ 0x2b740, 0x2b820, 0x2ceb0, 0x2f800, 0x30000, 0x31350, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint32_t eaw_lte32[] = {
+ 0x16fe3, 0x16ff1, 0x187f7, 0x18cd5, 0x18d08, 0x1aff3, 0x1affb, 0x1affe,
+ 0x1b122, 0x1b132, 0x1b152, 0x1b155, 0x1b167, 0x1b2fb, 0x1f004, 0x1f0cf,
+ 0x1f18e, 0x1f19a, 0x1f202, 0x1f23b, 0x1f248, 0x1f251, 0x1f265, 0x1f320,
+ 0x1f335, 0x1f37c, 0x1f393, 0x1f3ca, 0x1f3d3, 0x1f3f0, 0x1f3f4, 0x1f3fa,
+ 0x1f3ff, 0x1f43e, 0x1f440, 0x1f4fc, 0x1f53d, 0x1f54e, 0x1f567, 0x1f57a,
+ 0x1f596, 0x1f5a4, 0x1f64f, 0x1f6c5, 0x1f6cc, 0x1f6d2, 0x1f6d7, 0x1f6df,
+ 0x1f6ec, 0x1f6fc, 0x1f7eb, 0x1f7f0, 0x1f93a, 0x1f945, 0x1f9ff, 0x1fa7c,
+ 0x1fa88, 0x1fabd, 0x1fac5, 0x1fadb, 0x1fae8, 0x1faf8, 0x2a6df, 0x2b739,
+ 0x2b81d, 0x2cea1, 0x2ebe0, 0x2fa1d, 0x3134a, 0x323af, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t eaw_gte16[] = {
+ 0x3000, 0xff01, 0xffe0, 0x1100, 0x231a, 0x2329, 0x232a, 0x23e9, 0x23f0,
+ 0x23f3, 0x25fd, 0x2614, 0x2648, 0x267f, 0x2693, 0x26a1, 0x26aa, 0x26bd,
+ 0x26c4, 0x26ce, 0x26d4, 0x26ea, 0x26f2, 0x26f5, 0x26fa, 0x26fd, 0x2705,
+ 0x270a, 0x2728, 0x274c, 0x274e, 0x2753, 0x2757, 0x2795, 0x27b0, 0x27bf,
+ 0x2b1b, 0x2b50, 0x2b55, 0x2e80, 0x2e9b, 0x2f00, 0x2ff0, 0x3001, 0x302e,
+ 0x3041, 0x309b, 0x309d, 0x309f, 0x30a0, 0x30a1, 0x30fb, 0x30fc, 0x30ff,
+ 0x3105, 0x3131, 0x3190, 0x3192, 0x3196, 0x31a0, 0x31c0, 0x31f0, 0x3200,
+ 0x3220, 0x322a, 0x3250, 0x3251, 0x3260, 0x3280, 0x328a, 0x32b1, 0x32c0,
+ 0x3400, 0x4e00, 0xa015, 0xa016, 0xa490, 0xa960, 0xac00, 0xf900, 0xfa70,
+ 0xfe10, 0xfe30, 0xfe54, 0xfe68, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t eaw_lte16[] = {
+ 0x3000, 0xff60, 0xffe6, 0x115f, 0x231b, 0x2329, 0x232a, 0x23ec, 0x23f0,
+ 0x23f3, 0x25fe, 0x2615, 0x2653, 0x267f, 0x2693, 0x26a1, 0x26ab, 0x26be,
+ 0x26c5, 0x26ce, 0x26d4, 0x26ea, 0x26f3, 0x26f5, 0x26fa, 0x26fd, 0x2705,
+ 0x270b, 0x2728, 0x274c, 0x274e, 0x2755, 0x2757, 0x2797, 0x27b0, 0x27bf,
+ 0x2b1c, 0x2b50, 0x2b55, 0x2e99, 0x2ef3, 0x2fd5, 0x2ffb, 0x3029, 0x303e,
+ 0x3096, 0x309c, 0x309e, 0x309f, 0x30a0, 0x30fa, 0x30fb, 0x30fe, 0x30ff,
+ 0x312f, 0x318e, 0x3191, 0x3195, 0x319f, 0x31bf, 0x31e3, 0x31ff, 0x321e,
+ 0x3229, 0x3247, 0x3250, 0x325f, 0x327f, 0x3289, 0x32b0, 0x32bf, 0x33ff,
+ 0x4bdf, 0xa014, 0xa015, 0xa48c, 0xa4c6, 0xa97c, 0xd7a3, 0xfa6d, 0xfad9,
+ 0xfe19, 0xfe52, 0xfe66, 0xfe6b, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+/// These are the ranges of codepoints that are DEFINITELY width 0.
+HWY_ALIGN constexpr uint32_t zero_gte32[] = {
+ 0x110bd, 0x110cd, 0x13430, 0x1bca0, 0x1d173, 0xe0001, 0xe0020, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint32_t zero_lte32[] = {
+ 0x110bd, 0x110cd, 0x1343f, 0x1bca3, 0x1d17a, 0xe0001, 0xe007f, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t zero_gte16[] = {
+ 0xad, 0x70f, 0x890, 0x180e, 0x200b, 0x202a, 0x2060, 0x2066, 0xfeff,
+ 0xfff9, 0x488, 0x1abe, 0x20dd, 0x20e2, 0xa670, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t zero_lte16[] = {
+ 0xad, 0x70f, 0x891, 0x180e, 0x200f, 0x202e, 0x2064, 0x206f, 0xfeff,
+ 0xfffb, 0x489, 0x1abe, 0x20e0, 0x20e4, 0xa672, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+/// Non-spacing marks
+HWY_ALIGN constexpr uint32_t nsm_gte32[] = {
+ 0x101fd, 0x102e0, 0x10376, 0x10a01, 0x10a05, 0x10a0c, 0x10a38, 0x10a3f,
+ 0x10ae5, 0x10d24, 0x10eab, 0x10efd, 0x10f46, 0x10f82, 0x11001, 0x11038,
+ 0x11070, 0x11073, 0x1107f, 0x110b3, 0x110b9, 0x110c2, 0x11100, 0x11127,
+ 0x1112d, 0x11173, 0x11180, 0x111b6, 0x111c9, 0x111cf, 0x1122f, 0x11234,
+ 0x11236, 0x1123e, 0x11241, 0x112df, 0x112e3, 0x11300, 0x1133b, 0x11340,
+ 0x11366, 0x11370, 0x11438, 0x11442, 0x11446, 0x1145e, 0x114b3, 0x114ba,
+ 0x114bf, 0x114c2, 0x115b2, 0x115bc, 0x115bf, 0x115dc, 0x11633, 0x1163d,
+ 0x1163f, 0x116ab, 0x116ad, 0x116b0, 0x116b7, 0x1171d, 0x11722, 0x11727,
+ 0x1182f, 0x11839, 0x1193b, 0x1193e, 0x11943, 0x119d4, 0x119da, 0x119e0,
+ 0x11a01, 0x11a33, 0x11a3b, 0x11a47, 0x11a51, 0x11a59, 0x11a8a, 0x11a98,
+ 0x11c30, 0x11c38, 0x11c3f, 0x11c92, 0x11caa, 0x11cb2, 0x11cb5, 0x11d31,
+ 0x11d3a, 0x11d3c, 0x11d3f, 0x11d47, 0x11d90, 0x11d95, 0x11d97, 0x11ef3,
+ 0x11f00, 0x11f36, 0x11f40, 0x11f42, 0x13440, 0x13447, 0x16af0, 0x16b30,
+ 0x16f4f, 0x16f8f, 0x16fe4, 0x1bc9d, 0x1cf00, 0x1cf30, 0x1d167, 0x1d17b,
+ 0x1d185, 0x1d1aa, 0x1d242, 0x1da00, 0x1da3b, 0x1da75, 0x1da84, 0x1da9b,
+ 0x1daa1, 0x1e000, 0x1e008, 0x1e01b, 0x1e023, 0x1e026, 0x1e08f, 0x1e130,
+ 0x1e2ae, 0x1e2ec, 0x1e4ec, 0x1e8d0, 0x1e944, 0xe0100, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint32_t nsm_lte32[] = {
+ 0x101fd, 0x102e0, 0x1037a, 0x10a03, 0x10a06, 0x10a0f, 0x10a3a, 0x10a3f,
+ 0x10ae6, 0x10d27, 0x10eac, 0x10eff, 0x10f50, 0x10f85, 0x11001, 0x11046,
+ 0x11070, 0x11074, 0x11081, 0x110b6, 0x110ba, 0x110c2, 0x11102, 0x1112b,
+ 0x11134, 0x11173, 0x11181, 0x111be, 0x111cc, 0x111cf, 0x11231, 0x11234,
+ 0x11237, 0x1123e, 0x11241, 0x112df, 0x112ea, 0x11301, 0x1133c, 0x11340,
+ 0x1136c, 0x11374, 0x1143f, 0x11444, 0x11446, 0x1145e, 0x114b8, 0x114ba,
+ 0x114c0, 0x114c3, 0x115b5, 0x115bd, 0x115c0, 0x115dd, 0x1163a, 0x1163d,
+ 0x11640, 0x116ab, 0x116ad, 0x116b5, 0x116b7, 0x1171f, 0x11725, 0x1172b,
+ 0x11837, 0x1183a, 0x1193c, 0x1193e, 0x11943, 0x119d7, 0x119db, 0x119e0,
+ 0x11a0a, 0x11a38, 0x11a3e, 0x11a47, 0x11a56, 0x11a5b, 0x11a96, 0x11a99,
+ 0x11c36, 0x11c3d, 0x11c3f, 0x11ca7, 0x11cb0, 0x11cb3, 0x11cb6, 0x11d36,
+ 0x11d3a, 0x11d3d, 0x11d45, 0x11d47, 0x11d91, 0x11d95, 0x11d97, 0x11ef4,
+ 0x11f01, 0x11f3a, 0x11f40, 0x11f42, 0x13440, 0x13455, 0x16af4, 0x16b36,
+ 0x16f4f, 0x16f92, 0x16fe4, 0x1bc9e, 0x1cf2d, 0x1cf46, 0x1d169, 0x1d182,
+ 0x1d18b, 0x1d1ad, 0x1d244, 0x1da36, 0x1da6c, 0x1da75, 0x1da84, 0x1da9f,
+ 0x1daaf, 0x1e006, 0x1e018, 0x1e021, 0x1e024, 0x1e02a, 0x1e08f, 0x1e136,
+ 0x1e2ae, 0x1e2ef, 0x1e4ef, 0x1e8d6, 0x1e94a, 0xe01ef, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t nsm_gte16[] = {
+ 0x300, 0x483, 0x591, 0x5bf, 0x5c1, 0x5c4, 0x5c7, 0x610, 0x64b,
+ 0x670, 0x6d6, 0x6df, 0x6e7, 0x6ea, 0x711, 0x730, 0x7a6, 0x7eb,
+ 0x7fd, 0x816, 0x81b, 0x825, 0x829, 0x859, 0x898, 0x8ca, 0x8e3,
+ 0x93a, 0x93c, 0x941, 0x94d, 0x951, 0x962, 0x981, 0x9bc, 0x9c1,
+ 0x9cd, 0x9e2, 0x9fe, 0xa01, 0xa3c, 0xa41, 0xa47, 0xa4b, 0xa51,
+ 0xa70, 0xa75, 0xa81, 0xabc, 0xac1, 0xac7, 0xacd, 0xae2, 0xafa,
+ 0xb01, 0xb3c, 0xb3f, 0xb41, 0xb4d, 0xb55, 0xb62, 0xb82, 0xbc0,
+ 0xbcd, 0xc00, 0xc04, 0xc3c, 0xc3e, 0xc46, 0xc4a, 0xc55, 0xc62,
+ 0xc81, 0xcbc, 0xcbf, 0xcc6, 0xccc, 0xce2, 0xd00, 0xd3b, 0xd41,
+ 0xd4d, 0xd62, 0xd81, 0xdca, 0xdd2, 0xdd6, 0xe31, 0xe34, 0xe47,
+ 0xeb1, 0xeb4, 0xec8, 0xf18, 0xf35, 0xf37, 0xf39, 0xf71, 0xf80,
+ 0xf86, 0xf8d, 0xf99, 0xfc6, 0x102d, 0x1032, 0x1039, 0x103d, 0x1058,
+ 0x105e, 0x1071, 0x1082, 0x1085, 0x108d, 0x109d, 0x135d, 0x1712, 0x1732,
+ 0x1752, 0x1772, 0x17b4, 0x17b7, 0x17c6, 0x17c9, 0x17dd, 0x180b, 0x180f,
+ 0x1885, 0x18a9, 0x1920, 0x1927, 0x1932, 0x1939, 0x1a17, 0x1a1b, 0x1a56,
+ 0x1a58, 0x1a60, 0x1a62, 0x1a65, 0x1a73, 0x1a7f, 0x1ab0, 0x1abf, 0x1b00,
+ 0x1b34, 0x1b36, 0x1b3c, 0x1b42, 0x1b6b, 0x1b80, 0x1ba2, 0x1ba8, 0x1bab,
+ 0x1be6, 0x1be8, 0x1bed, 0x1bef, 0x1c2c, 0x1c36, 0x1cd0, 0x1cd4, 0x1ce2,
+ 0x1ced, 0x1cf4, 0x1cf8, 0x1dc0, 0x20d0, 0x20e1, 0x20e5, 0x2cef, 0x2d7f,
+ 0x2de0, 0x302a, 0x3099, 0xa66f, 0xa674, 0xa69e, 0xa6f0, 0xa802, 0xa806,
+ 0xa80b, 0xa825, 0xa82c, 0xa8c4, 0xa8e0, 0xa8ff, 0xa926, 0xa947, 0xa980,
+ 0xa9b3, 0xa9b6, 0xa9bc, 0xa9e5, 0xaa29, 0xaa31, 0xaa35, 0xaa43, 0xaa4c,
+ 0xaa7c, 0xaab0, 0xaab2, 0xaab7, 0xaabe, 0xaac1, 0xaaec, 0xaaf6, 0xabe5,
+ 0xabe8, 0xabed, 0xfb1e, 0xfe00, 0xfe20, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+HWY_ALIGN constexpr uint16_t nsm_lte16[] = {
+ 0x36f, 0x487, 0x5bd, 0x5bf, 0x5c2, 0x5c5, 0x5c7, 0x61a, 0x65f,
+ 0x670, 0x6dc, 0x6e4, 0x6e8, 0x6ed, 0x711, 0x74a, 0x7b0, 0x7f3,
+ 0x7fd, 0x819, 0x823, 0x827, 0x82d, 0x85b, 0x89f, 0x8e1, 0x902,
+ 0x93a, 0x93c, 0x948, 0x94d, 0x957, 0x963, 0x981, 0x9bc, 0x9c4,
+ 0x9cd, 0x9e3, 0x9fe, 0xa02, 0xa3c, 0xa42, 0xa48, 0xa4d, 0xa51,
+ 0xa71, 0xa75, 0xa82, 0xabc, 0xac5, 0xac8, 0xacd, 0xae3, 0xaff,
+ 0xb01, 0xb3c, 0xb3f, 0xb44, 0xb4d, 0xb56, 0xb63, 0xb82, 0xbc0,
+ 0xbcd, 0xc00, 0xc04, 0xc3c, 0xc40, 0xc48, 0xc4d, 0xc56, 0xc63,
+ 0xc81, 0xcbc, 0xcbf, 0xcc6, 0xccd, 0xce3, 0xd01, 0xd3c, 0xd44,
+ 0xd4d, 0xd63, 0xd81, 0xdca, 0xdd4, 0xdd6, 0xe31, 0xe3a, 0xe4e,
+ 0xeb1, 0xebc, 0xece, 0xf19, 0xf35, 0xf37, 0xf39, 0xf7e, 0xf84,
+ 0xf87, 0xf97, 0xfbc, 0xfc6, 0x1030, 0x1037, 0x103a, 0x103e, 0x1059,
+ 0x1060, 0x1074, 0x1082, 0x1086, 0x108d, 0x109d, 0x135f, 0x1714, 0x1733,
+ 0x1753, 0x1773, 0x17b5, 0x17bd, 0x17c6, 0x17d3, 0x17dd, 0x180d, 0x180f,
+ 0x1886, 0x18a9, 0x1922, 0x1928, 0x1932, 0x193b, 0x1a18, 0x1a1b, 0x1a56,
+ 0x1a5e, 0x1a60, 0x1a62, 0x1a6c, 0x1a7c, 0x1a7f, 0x1abd, 0x1ace, 0x1b03,
+ 0x1b34, 0x1b3a, 0x1b3c, 0x1b42, 0x1b73, 0x1b81, 0x1ba5, 0x1ba9, 0x1bad,
+ 0x1be6, 0x1be9, 0x1bed, 0x1bf1, 0x1c33, 0x1c37, 0x1cd2, 0x1ce0, 0x1ce8,
+ 0x1ced, 0x1cf4, 0x1cf9, 0x1dff, 0x20dc, 0x20e1, 0x20f0, 0x2cf1, 0x2d7f,
+ 0x2dff, 0x302d, 0x309a, 0xa66f, 0xa67d, 0xa69f, 0xa6f1, 0xa802, 0xa806,
+ 0xa80b, 0xa826, 0xa82c, 0xa8c5, 0xa8f1, 0xa8ff, 0xa92d, 0xa951, 0xa982,
+ 0xa9b3, 0xa9b9, 0xa9bd, 0xa9e5, 0xaa2e, 0xaa32, 0xaa36, 0xaa43, 0xaa4c,
+ 0xaa7c, 0xaab0, 0xaab4, 0xaab8, 0xaabf, 0xaac1, 0xaaed, 0xaaf6, 0xabe5,
+ 0xabe8, 0xabed, 0xfb1e, 0xfe0f, 0xfe2f, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+// All our tables must be identically sized
+static_assert(std::size(eaw_gte32) == std::size(eaw_lte32));
+static_assert(std::size(eaw_gte16) == std::size(eaw_lte16));
+static_assert(std::size(zero_gte32) == std::size(zero_lte32));
+static_assert(std::size(zero_gte16) == std::size(zero_lte16));
+static_assert(std::size(nsm_gte32) == std::size(nsm_lte32));
+static_assert(std::size(nsm_gte16) == std::size(nsm_lte16));
+
+/// Handles 16-bit codepoints.
+template <class D, typename T = uint16_t>
+int8_t CodepointWidth16(D d, uint16_t input) {
+ assert(input > 0xFF);
+ assert(input <= 0xFFFF);
+
+ const size_t N = hn::Lanes(d);
+ const hn::Vec<D> input_vec = Set(d, input);
+
+ {
+ // NOTE: 0x2E3B is technically width 3 but for our terminal we only
+ // handle up to width 2 as wide so we will treat it as width 2.
+ HWY_ALIGN constexpr T gte_keys[] = {
+ 0x2E3A, 0x3400, 0x4E00, 0xF900, 0x2E3B, 0x1160, 0x2060, 0xFFF0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ HWY_ALIGN constexpr T lte_keys[] = {
+ 0x2E3A, 0x4DBF, 0x9FFF, 0xFAFF, 0x2E3B, 0x11FF, 0x206F, 0xFFF8, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ static_assert(std::size(gte_keys) == std::size(lte_keys));
+ static_assert(std::size(gte_keys) >= 32);
+ size_t i = 0;
+ for (; i + N <= std::size(lte_keys) && lte_keys[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, lte_keys + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, gte_keys + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+
+ // We organize the data above to split 0 and 2-width codepoints since
+ // we can probably do all the comparisons in one go.
+ if (idx >= 5) {
+ return 0;
+ } else if (idx >= 0) {
+ return 2;
+ }
+ }
+ assert(i >= 7); // We should have checked all the ranges.
+ }
+
+ {
+ constexpr T zero_gte_min =
+ *std::min_element(zero_gte16, zero_gte16 + std::size(zero_gte16));
+ constexpr T zero_lte_max =
+ *std::max_element(zero_lte16, zero_lte16 + std::size(zero_lte16));
+ if (input >= zero_gte_min && input <= zero_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(zero_gte16) && zero_gte16[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, zero_lte16 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, zero_gte16 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ {
+ constexpr T eaw_gte_min =
+ *std::min_element(eaw_gte16, eaw_gte16 + std::size(eaw_gte16));
+ constexpr T eaw_lte_max =
+ *std::max_element(eaw_lte16, eaw_lte16 + std::size(eaw_lte16));
+ if (input >= eaw_gte_min && input <= eaw_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(eaw_lte16) && eaw_lte16[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, eaw_lte16 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, eaw_gte16 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 2;
+ }
+ }
+ }
+ }
+
+ {
+ constexpr T nsm_gte_min =
+ *std::min_element(nsm_gte16, nsm_gte16 + std::size(nsm_gte16));
+ constexpr T nsm_lte_max =
+ *std::max_element(nsm_lte16, nsm_lte16 + std::size(nsm_lte16));
+ if (input >= nsm_gte_min && input <= nsm_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(nsm_lte16) && nsm_lte16[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, nsm_lte16 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, nsm_gte16 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+/// Handles codepoints larger than 16-bit.
+template <class D, typename T = uint32_t>
+int8_t CodepointWidth32(D d, T input) {
+ assert(input > 0xFFFF);
+
+ const size_t N = hn::Lanes(d);
+ const hn::Vec<D> input_vec = Set(d, input);
+
+ {
+ // NOTE: 0x2E3B is technically width 3 but for our terminal we only
+ // handle up to width 2 as wide so we will treat it as width 2.
+ HWY_ALIGN constexpr T gte_keys[] = {
+ 0x1f1e6, 0x20000, 0x30000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ HWY_ALIGN constexpr T lte_keys[] = {
+ 0x1f1ff, 0x2FFFD, 0x3FFFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ static_assert(std::size(gte_keys) == std::size(lte_keys));
+ static_assert(std::size(gte_keys) >= 16);
+ size_t i = 0;
+ for (; i + N <= std::size(lte_keys) && lte_keys[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, lte_keys + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, gte_keys + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 2;
+ }
+ }
+ assert(i >= 2); // We should have checked all the ranges.
+ }
+
+ {
+ HWY_ALIGN constexpr T gte_keys[] = {
+ 0xE0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ HWY_ALIGN constexpr T lte_keys[] = {
+ 0xE0FFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ static_assert(std::size(gte_keys) == std::size(lte_keys));
+ static_assert(std::size(gte_keys) >= 16);
+ size_t i = 0;
+ for (; i + N <= std::size(lte_keys) && lte_keys[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, lte_keys + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, gte_keys + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 0;
+ }
+ }
+ }
+
+ {
+ constexpr T zero_gte_min =
+ *std::min_element(zero_gte32, zero_gte32 + std::size(zero_gte32));
+ constexpr T zero_lte_max =
+ *std::max_element(zero_lte32, zero_lte32 + std::size(zero_lte32));
+ if (input >= zero_gte_min && input <= zero_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(zero_gte32) && zero_gte32[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, zero_lte32 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, zero_gte32 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ {
+ constexpr T eaw_gte_min =
+ *std::min_element(eaw_gte32, eaw_gte32 + std::size(eaw_gte32));
+ constexpr T eaw_lte_max =
+ *std::max_element(eaw_lte32, eaw_lte32 + std::size(eaw_lte32));
+ if (input >= eaw_gte_min && input <= eaw_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(eaw_lte32) && eaw_lte32[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, eaw_lte32 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, eaw_gte32 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 2;
+ }
+ }
+ }
+ }
+
+ {
+ constexpr T nsm_gte_min =
+ *std::min_element(nsm_gte32, nsm_gte32 + std::size(nsm_gte32));
+ constexpr T nsm_lte_max =
+ *std::max_element(nsm_lte32, nsm_lte32 + std::size(nsm_lte32));
+ if (input >= nsm_gte_min && input <= nsm_lte_max) {
+ size_t i = 0;
+ for (; i + N <= std::size(nsm_lte32) && nsm_lte32[i] != 0; i += N) {
+ const hn::Vec<D> lte_vec = hn::Load(d, nsm_lte32 + i);
+ const hn::Vec<D> gte_vec = hn::Load(d, nsm_gte32 + i);
+ const intptr_t idx = hn::FindFirstTrue(
+ d, hn::And(hn::Le(input_vec, lte_vec), hn::Ge(input_vec, gte_vec)));
+ if (idx >= 0) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+/// Vectorized implementation of Unicode display width. Determining width
+/// unfortunately requires many small range checks, so we test some fast paths
+/// and otherwise try to do N (vector lane width) range checks at a time.
+int8_t CodepointWidth(uint32_t input) {
+ // If the input is ASCII, then we return 1. We do NOT check for
+ // control characters because we assume that the input has already
+ // been checked for that case.
+ if (input <= 0xFF) {
+ return 1;
+ }
+
+ // We handle 16-bit codepoints separately because they are more common
+ // and we can fit double the number of lanes in a vector.
+ if (input <= 0xFFFF) {
+ hn::ScalableTag<uint16_t> d;
+ return CodepointWidth16(d, input);
+ }
+
+ hn::ScalableTag<uint32_t> d;
+ return CodepointWidth32(d, input);
+}
+
+} // namespace HWY_NAMESPACE
+} // namespace ghostty
+HWY_AFTER_NAMESPACE();
+
+// HWY_ONCE is true for only one of the target passes
+#if HWY_ONCE
+
+namespace ghostty {
+
+HWY_EXPORT(CodepointWidth);
+
+int8_t CodepointWidth(uint32_t cp) {
+ return HWY_DYNAMIC_DISPATCH(CodepointWidth)(cp);
+}
+
+} // namespace ghostty
+
+extern "C" {
+
+int8_t ghostty_simd_codepoint_width(uint32_t cp) {
+ return ghostty::CodepointWidth(cp);
+}
+
+} // extern "C"
+
+#endif // HWY_ONCE
diff --git a/vendor/libghostty-vt/src/simd/codepoint_width.zig b/vendor/libghostty-vt/src/simd/codepoint_width.zig
new file mode 100644
index 00000000..c1767bea
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/codepoint_width.zig
@@ -0,0 +1,52 @@
+const std = @import("std");
+const options = @import("build_options");
+
+// vt.cpp
+extern "c" fn ghostty_simd_codepoint_width(u32) i8;
+
+pub fn codepointWidth(cp: u32) i8 {
+ if (comptime options.simd) return ghostty_simd_codepoint_width(cp);
+ const uucode = @import("uucode");
+ if (cp > uucode.config.max_code_point) return 1;
+ return @import("uucode").get(.width, @intCast(cp));
+}
+
+test "codepointWidth basic" {
+ const testing = std.testing;
+ try testing.expectEqual(@as(i8, 1), codepointWidth('a'));
+ try testing.expectEqual(@as(i8, 1), codepointWidth(0x100)); // Ā
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x3400)); // 㐀
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x2E3A)); // ⸺
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x1F1E6)); // 🇦
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x4E00)); // 一
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0xF900)); // 豈
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x20000)); // 𠀀
+ try testing.expectEqual(@as(i8, 2), codepointWidth(0x30000)); // 𠀀
+ // try testing.expectEqual(@as(i8, 1), @import("uucode").get(.width, 0x100));
+}
+
+// This is not very fast in debug modes, so its commented by default.
+// IMPORTANT: UNCOMMENT THIS WHENEVER MAKING CODEPOINTWIDTH CHANGES.
+// test "codepointWidth matches uucode" {
+// const testing = std.testing;
+// const uucode = @import("uucode");
+//
+// const min = 0xFF + 1; // start outside ascii
+// const max = std.math.maxInt(u21) + 1;
+// for (min..max) |cp| {
+// const simd = codepointWidth(@intCast(cp));
+// const uu = if (cp > uucode.config.max_code_point)
+// 1
+// else
+// uucode.get(.width, @intCast(cp));
+// if (simd != uu) mismatch: {
+// if (cp == 0x2E3B) {
+// try testing.expectEqual(@as(i8, 2), simd);
+// break :mismatch;
+// }
+//
+// std.log.warn("mismatch cp=U+{x} simd={} uucode={}", .{ cp, simd, uu });
+// try testing.expect(false);
+// }
+// }
+// }
diff --git a/vendor/libghostty-vt/src/simd/index_of.cpp b/vendor/libghostty-vt/src/simd/index_of.cpp
new file mode 100644
index 00000000..b070ae3b
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/index_of.cpp
@@ -0,0 +1,53 @@
+// Generates code for every target that this compiler can support.
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "simd/index_of.cpp" // this file
+#include <hwy/foreach_target.h> // must come before highway.h
+#include <hwy/highway.h>
+
+#include <simd/index_of.h>
+
+#include <optional>
+
+HWY_BEFORE_NAMESPACE();
+namespace ghostty {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+size_t IndexOf(const uint8_t needle,
+ const uint8_t* HWY_RESTRICT input,
+ size_t count) {
+ const hn::ScalableTag<uint8_t> d;
+ return IndexOfImpl(d, needle, input, count);
+}
+
+} // namespace HWY_NAMESPACE
+} // namespace ghostty
+HWY_AFTER_NAMESPACE();
+
+// HWY_ONCE is true for only one of the target passes
+#if HWY_ONCE
+
+namespace ghostty {
+
+// This macro declares a static array used for dynamic dispatch.
+HWY_EXPORT(IndexOf);
+
+size_t IndexOf(const uint8_t needle,
+ const uint8_t* HWY_RESTRICT input,
+ size_t count) {
+ return HWY_DYNAMIC_DISPATCH(IndexOf)(needle, input, count);
+}
+
+} // namespace ghostty
+
+extern "C" {
+
+size_t ghostty_simd_index_of(const uint8_t needle,
+ const uint8_t* HWY_RESTRICT input,
+ size_t count) {
+ return ghostty::IndexOf(needle, input, count);
+}
+}
+
+#endif // HWY_ONCE
diff --git a/vendor/libghostty-vt/src/simd/index_of.h b/vendor/libghostty-vt/src/simd/index_of.h
new file mode 100644
index 00000000..531af9f8
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/index_of.h
@@ -0,0 +1,98 @@
+#if defined(GHOSTTY_SIMD_INDEX_OF_H_) == defined(HWY_TARGET_TOGGLE)
+#ifdef GHOSTTY_SIMD_INDEX_OF_H_
+#undef GHOSTTY_SIMD_INDEX_OF_H_
+#else
+#define GHOSTTY_SIMD_INDEX_OF_H_
+#endif
+
+#include <hwy/highway.h>
+
+#include <cstddef>
+#include <optional>
+
+HWY_BEFORE_NAMESPACE();
+namespace ghostty {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+// Return the index of the first occurrence of `needle` in `input`, where
+// the input and needle are already loaded into vectors.
+template <class D, typename T = hn::TFromD<D>>
+std::optional<size_t> IndexOfChunk(D d,
+ hn::Vec<D> needle_vec,
+ hn::Vec<D> input_vec) {
+ // Compare the input vector with the needle vector. This produces
+ // a vector where each lane is 0xFF if the corresponding lane in
+ // `input_vec` is equal to the corresponding lane in `needle_vec`.
+ const hn::Mask<D> eq_mask = hn::Eq(needle_vec, input_vec);
+
+ // Find the index within the vector where the first true value is.
+ const intptr_t pos = hn::FindFirstTrue(d, eq_mask);
+
+ // If we found a match, return the index into the input.
+ if (pos >= 0) {
+ return std::optional<size_t>(static_cast<size_t>(pos));
+ } else {
+ return std::nullopt;
+ }
+}
+
+// Return the index of the first occurrence of `needle` in `input` or
+// `count` if not found.
+template <class D, typename T = hn::TFromD<D>>
+size_t IndexOfImpl(D d, T needle, const T* HWY_RESTRICT input, size_t count) {
+ // Note: due to the simplicity of this operation and the general complexity
+ // of SIMD, I'm going to overly comment this function to help explain the
+ // implementation for future maintainers.
+
+ // The number of lanes in the vector type.
+ const size_t N = hn::Lanes(d);
+
+ // Create a vector with all lanes set to `needle` so we can do a lane-wise
+ // comparison with the input.
+ const hn::Vec<D> needle_vec = Set(d, needle);
+
+ // Compare N elements at a time.
+ size_t i = 0;
+ for (; i + N <= count; i += N) {
+ // Load the N elements from our input into a vector and check the chunk.
+ const hn::Vec<D> input_vec = hn::LoadU(d, input + i);
+ if (auto pos = IndexOfChunk(d, needle_vec, input_vec)) {
+ return i + pos.value();
+ }
+ }
+
+ // Since we compare N elements at a time, we may have some elements left
+ // if count modulo N != 0. We need to scan the remaining elements. To
+ // be simple, we search one element at a time.
+ if (i != count) {
+ // Create a new vector with only one relevant lane.
+ const hn::CappedTag<T, 1> d1;
+ using D1 = decltype(d1);
+
+ // Get an equally sized needle vector with only one lane.
+ const hn::Vec<D1> needle1 = Set(d1, hn::GetLane(needle_vec));
+
+ // Go through the remaining elements and do similar logic to
+ // the previous loop to find any matches.
+ for (; i < count; ++i) {
+ const hn::Vec<D1> input_vec = hn::LoadU(d1, input + i);
+ const hn::Mask<D1> eq_mask = hn::Eq(needle1, input_vec);
+ if (hn::AllTrue(d1, eq_mask))
+ return i;
+ }
+ }
+
+ return count;
+}
+
+size_t IndexOf(const uint8_t needle,
+ const uint8_t* HWY_RESTRICT input,
+ size_t count);
+
+} // namespace HWY_NAMESPACE
+} // namespace ghostty
+HWY_AFTER_NAMESPACE();
+
+#endif // GHOSTTY_SIMD_INDEX_OF_H_
diff --git a/vendor/libghostty-vt/src/simd/index_of.zig b/vendor/libghostty-vt/src/simd/index_of.zig
new file mode 100644
index 00000000..7bf053b0
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/index_of.zig
@@ -0,0 +1,35 @@
+const std = @import("std");
+const options = @import("build_options");
+
+extern "c" fn ghostty_simd_index_of(
+ needle: u8,
+ input: [*]const u8,
+ count: usize,
+) usize;
+
+pub fn indexOf(input: []const u8, needle: u8) ?usize {
+ if (comptime options.simd) {
+ const result = ghostty_simd_index_of(needle, input.ptr, input.len);
+ return if (result == input.len) null else result;
+ }
+
+ return indexOfScalar(input, needle);
+}
+
+fn indexOfScalar(input: []const u8, needle: u8) ?usize {
+ return std.mem.indexOfScalar(u8, input, needle);
+}
+
+test "indexOf" {
+ const testing = std.testing;
+ try testing.expect(indexOf("hello", ' ') == null);
+ try testing.expectEqual(@as(usize, 2), indexOf("hi lo", ' ').?);
+ try testing.expectEqual(@as(usize, 5), indexOf(
+ \\XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ \\XXXXXXXXXXXX XXXXXXXXXXX XXXXXXXXXXXXXXX
+ , ' ').?);
+ try testing.expectEqual(@as(usize, 53), indexOf(
+ \\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ \\XXXXXXXXXXXX XXXXXXXXXXX XXXXXXXXXXXXXXX
+ , ' ').?);
+}
diff --git a/vendor/libghostty-vt/src/simd/main.zig b/vendor/libghostty-vt/src/simd/main.zig
new file mode 100644
index 00000000..aabdd21d
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/main.zig
@@ -0,0 +1,14 @@
+//! SIMD-optimized routines. If `build_options.simd` is false, then the API
+//! still works but we fall back to pure Zig scalar implementations.
+
+const std = @import("std");
+
+const codepoint_width = @import("codepoint_width.zig");
+pub const base64 = @import("base64.zig");
+pub const index_of = @import("index_of.zig");
+pub const vt = @import("vt.zig");
+pub const codepointWidth = codepoint_width.codepointWidth;
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/simd/vt.cpp b/vendor/libghostty-vt/src/simd/vt.cpp
new file mode 100644
index 00000000..d8133abf
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/vt.cpp
@@ -0,0 +1,169 @@
+// Generates code for every target that this compiler can support.
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "simd/vt.cpp" // this file
+#include <hwy/foreach_target.h> // must come before highway.h
+#include <hwy/highway.h>
+
+#include <simdutf.h>
+#include <utf8.h>
+#include <vector>
+
+#include <simd/index_of.h>
+#include <simd/vt.h>
+
+HWY_BEFORE_NAMESPACE();
+namespace ghostty {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+using T = uint8_t;
+
+// Decode the UTF-8 text in input into output. Returns the number of decoded
+// characters. This function assumes output is large enough.
+//
+// This function handles malformed UTF-8 sequences by inserting a
+// replacement character (U+FFFD) and continuing to decode. This function
+// will consume the entire input no matter what.
+size_t DecodeUTF8(const uint8_t* HWY_RESTRICT input,
+ size_t count,
+ char32_t* output) {
+ // Its possible for our input to be empty since DecodeUTF8UntilControlSeq
+ // doesn't check for this.
+ if (count == 0) {
+ return 0;
+ }
+
+ // Assume no errors for fast path.
+ const size_t decoded = simdutf::convert_utf8_to_utf32(
+ reinterpret_cast<const char*>(input), count, output);
+ if (decoded > 0) {
+ return decoded;
+ }
+
+ // Errors in the UTF input, take a slow path and do a decode with
+ // replacement (with U+FFFD). Note that simdutf doesn't have a
+ // decode with replacement API:
+ // https://github.com/simdutf/simdutf/issues/147
+ //
+ // Because of this, we use a separate library with heap allocation
+ // that is much, much slower (the allocation is slower, the algorithm
+ // is slower, etc.) This is just so we have something that works.
+ // I want to replace this.
+ std::vector<char> replacement_result;
+ utf8::replace_invalid(input, input + count,
+ std::back_inserter(replacement_result), 0xFFFD);
+ return DecodeUTF8(reinterpret_cast<const uint8_t*>(replacement_result.data()),
+ replacement_result.size(), output);
+}
+
+/// Decode the UTF-8 text in input into output until an escape
+/// character is found. This returns the number of bytes consumed
+/// from input and writes the number of decoded characters into
+/// output_count.
+///
+/// This may return a value less than count even with no escape
+/// character if the input ends with an incomplete UTF-8 sequence.
+/// The caller should check the next byte manually to determine
+/// if it is incomplete.
+template <class D>
+size_t DecodeUTF8UntilControlSeqImpl(D d,
+ const T* HWY_RESTRICT input,
+ size_t count,
+ char32_t* output,
+ size_t* output_count) {
+ const size_t N = hn::Lanes(d);
+
+ // Create a vector containing ESC since that denotes a control sequence.
+ const hn::Vec<D> esc_vec = Set(d, 0x1B);
+
+ // Compare N elements at a time.
+ size_t i = 0;
+ for (; i + N <= count; i += N) {
+ // Load the N elements from our input into a vector.
+ const hn::Vec<D> input_vec = hn::LoadU(d, input + i);
+
+ // If we don't have any escapes we keep going. We want to accumulate
+ // the largest possible valid UTF-8 sequence before decoding.
+ // TODO(mitchellh): benchmark this vs decoding every time
+ const auto esc_idx = IndexOfChunk(d, esc_vec, input_vec);
+ if (!esc_idx) {
+ continue;
+ }
+
+ // We have an ESC char, decode up to this point. We start by assuming
+ // a valid UTF-8 sequence and slow-path into error handling if we find
+ // an invalid sequence.
+ *output_count = DecodeUTF8(input, i + esc_idx.value(), output);
+ return i + esc_idx.value();
+ }
+
+ // If we have leftover input then we decode it one byte at a time (slow!)
+ // using pretty much the same logic as above.
+ if (i != count) {
+ const hn::CappedTag<T, 1> d1;
+ using D1 = decltype(d1);
+ const hn::Vec<D1> esc1 = Set(d1, hn::GetLane(esc_vec));
+ for (; i < count; ++i) {
+ const hn::Vec<D1> input_vec = hn::LoadU(d1, input + i);
+ const auto esc_idx = IndexOfChunk(d1, esc1, input_vec);
+ if (!esc_idx) {
+ continue;
+ }
+
+ *output_count = DecodeUTF8(input, i + esc_idx.value(), output);
+ return i + esc_idx.value();
+ }
+ }
+
+ // If we reached this point, its possible for our input to have an
+ // incomplete sequence because we're consuming the full input. We need
+ // to trim any incomplete sequences from the end of the input.
+ const size_t trimmed_len =
+ simdutf::trim_partial_utf8(reinterpret_cast<const char*>(input), i);
+ *output_count = DecodeUTF8(input, trimmed_len, output);
+ return trimmed_len;
+}
+
+size_t DecodeUTF8UntilControlSeq(const uint8_t* HWY_RESTRICT input,
+ size_t count,
+ char32_t* output,
+ size_t* output_count) {
+ const hn::ScalableTag<uint8_t> d;
+ return DecodeUTF8UntilControlSeqImpl(d, input, count, output, output_count);
+}
+
+} // namespace HWY_NAMESPACE
+} // namespace ghostty
+HWY_AFTER_NAMESPACE();
+
+// HWY_ONCE is true for only one of the target passes
+#if HWY_ONCE
+
+namespace ghostty {
+
+HWY_EXPORT(DecodeUTF8UntilControlSeq);
+
+size_t DecodeUTF8UntilControlSeq(const uint8_t* HWY_RESTRICT input,
+ size_t count,
+ char32_t* output,
+ size_t* output_count) {
+ return HWY_DYNAMIC_DISPATCH(DecodeUTF8UntilControlSeq)(input, count, output,
+ output_count);
+}
+
+} // namespace ghostty
+
+extern "C" {
+
+size_t ghostty_simd_decode_utf8_until_control_seq(const uint8_t* HWY_RESTRICT
+ input,
+ size_t count,
+ char32_t* output,
+ size_t* output_count) {
+ return ghostty::DecodeUTF8UntilControlSeq(input, count, output, output_count);
+}
+
+} // extern "C"
+
+#endif // HWY_ONCE
diff --git a/vendor/libghostty-vt/src/simd/vt.h b/vendor/libghostty-vt/src/simd/vt.h
new file mode 100644
index 00000000..c54ae93f
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/vt.h
@@ -0,0 +1,30 @@
+#if defined(GHOSTTY_SIMD_VT_H_) == defined(HWY_TARGET_TOGGLE)
+#ifdef GHOSTTY_SIMD_VT_H_
+#undef GHOSTTY_SIMD_VT_H_
+#else
+#define GHOSTTY_SIMD_VT_H_
+#endif
+
+#include <hwy/highway.h>
+
+HWY_BEFORE_NAMESPACE();
+namespace ghostty {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+} // namespace HWY_NAMESPACE
+} // namespace ghostty
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+
+namespace ghostty {
+
+typedef void (*PrintFunc)(const char32_t* chars, size_t count);
+
+} // namespace ghostty
+
+#endif // HWY_ONCE
+
+#endif // GHOSTTY_SIMD_VT_H_
diff --git a/vendor/libghostty-vt/src/simd/vt.zig b/vendor/libghostty-vt/src/simd/vt.zig
new file mode 100644
index 00000000..fa8754fa
--- /dev/null
+++ b/vendor/libghostty-vt/src/simd/vt.zig
@@ -0,0 +1,196 @@
+const std = @import("std");
+const options = @import("build_options");
+const assert = @import("../quirks.zig").inlineAssert;
+const indexOf = @import("index_of.zig").indexOf;
+
+// 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 {
+ assert(output.len >= input.len);
+
+ if (comptime options.simd) {
+ 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 };
+ }
+
+ return utf8DecodeUntilControlSeqScalar(input, output);
+}
+
+fn utf8DecodeUntilControlSeqScalar(
+ input: []const u8,
+ output: []u32,
+) DecodeResult {
+ // Find our escape
+ const idx = indexOf(input, 0x1B) orelse input.len;
+ const decode = input[0..idx];
+
+ // Go through and decode one item at a time.
+ var decode_offset: usize = 0;
+ var decode_count: usize = 0;
+ while (decode_offset < decode.len) {
+ const decode_rem = decode[decode_offset..];
+ const cp_len = std.unicode.utf8ByteSequenceLength(decode_rem[0]) catch {
+ // Note, this is matching our SIMD behavior, but it is admittedly
+ // a bit weird. See our "decode invalid leading byte" test too.
+ // SIMD should be our source of truth then we copy behavior here.
+ break;
+ };
+
+ // If we don't have that number of bytes available. we finish. We
+ // assume this is a partial input and we defer to the future.
+ if (decode_rem.len < cp_len) break;
+
+ // We have the bytes available, so move forward
+ const cp_bytes = decode_rem[0..cp_len];
+ decode_offset += cp_len;
+ if (std.unicode.utf8Decode(cp_bytes)) |cp| {
+ output[decode_count] = @intCast(cp);
+ decode_count += 1;
+ } else |_| {
+ // If decoding failed, we replace the leading byte with the
+ // replacement char and then continue decoding after that
+ // byte. This matches the SIMD behavior and is tested by the
+ // "invalid UTF-8" tests.
+ output[decode_count] = 0xFFFD;
+ decode_count += 1;
+ decode_offset -= cp_len - 1;
+ }
+ }
+
+ return .{
+ .consumed = decode_offset,
+ .decoded = decode_count,
+ };
+}
+
+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 2-byte sequence
+ {
+ const str = "hello\xc2\x01";
+ try testing.expectEqual(DecodeResult{
+ .consumed = 7,
+ .decoded = 7,
+ }, utf8DecodeUntilControlSeq(str, &output));
+ }
+
+ // Replacement will only replace the invalid leading byte.
+ try testing.expectEqual(@as(u32, 0xFFFD), output[5]);
+ try testing.expectEqual(@as(u32, 0x01), output[6]);
+}
+
+// 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));
+ }
+}