//! uucode build config shared by ghostty-vt and libvaxis. //! //! This is a copy of ghostty's `src/build/uucode_config.zig` (so ghostty-vt //! gets exactly the fields/components it expects), with one change: every //! table is forced to `.packing = .unpacked`. //! //! Why: libvaxis calls `uucode.get(...)`, which comptime-forces evaluation of //! the whole generated `tables` value. With the default `.auto` packing the //! `buildtime` table is emitted as a packed `@bitCast` of a huge `[N]u10` //! array, and Zig 0.16.0 cannot evaluate that at comptime (it panics with //! "TODO implement writeToPackedMemory for more types"). ghostty-vt itself //! never forces that evaluation, so ghostty's own build doesn't hit it, but we //! do the moment vaxis touches uucode. Unpacked tables are byte-aligned and //! evaluate fine (see also the raised eval branch quota in main.zig). const std = @import("std"); const assert = std.debug.assert; const config = @import("config.zig"); const Allocator = std.mem.Allocator; pub const fields = &config.mergeFields(config.fields, &.{ .{ .name = "width", .type = u2 }, .{ .name = "is_symbol", .type = bool }, }); pub const build_components = &config.mergeComponents(config.build_components, &.{ .{ .Impl = WidthComponent, .inputs = &.{ "wcwidth_standalone", "wcwidth_zero_in_grapheme", "is_emoji_modifier", "grapheme_break_no_control", }, .fields = &.{"width"}, }, .{ .Impl = IsSymbolComponent, .inputs = &.{ "block", "general_category" }, .fields = &.{"is_symbol"}, }, }); pub const get_components: []const config.Component = &.{}; pub const tables = [_]config.Table{ .{ .name = "runtime", .packing = .unpacked, .fields = &.{ "is_emoji_presentation", "case_folding_full", }, }, .{ // Fields that libvaxis needs that aren't included in the `runtime` // table. .name = "libvaxis_only", .packing = .unpacked, .fields = &.{ "east_asian_width", "general_category", "grapheme_break", }, }, .{ .name = "buildtime", .packing = .unpacked, .fields = &.{ "width", "wcwidth_zero_in_grapheme", "grapheme_break_no_control", "is_symbol", "is_emoji_vs_base", }, }, }; const WidthComponent = struct { pub fn build( comptime InputRow: type, comptime Row: type, allocator: std.mem.Allocator, io: std.Io, inputs: config.MultiSlice(InputRow), rows: *config.MultiSlice(Row), backing: anytype, tracking: anytype, ) !void { _ = allocator; _ = io; _ = backing; _ = tracking; rows.len = config.num_code_points; const items = rows.items(.width); const standalone = inputs.items(.wcwidth_standalone); const zero_in_grapheme = inputs.items(.wcwidth_zero_in_grapheme); const is_emoji_modifier = inputs.items(.is_emoji_modifier); const grapheme_break_no_control = inputs.items(.grapheme_break_no_control); // This condition is needed as Ghostty currently has a singular concept for // the `width` of a code point, while `uucode` splits the concept into // `wcwidth_standalone` and `wcwidth_zero_in_grapheme`. The two cases where // we want to use the `wcwidth_standalone` despite the code point occupying // zero width in a grapheme (`wcwidth_zero_in_grapheme`) are emoji // modifiers and prepend code points. For emoji modifiers we want to // support displaying them in isolation as color patches, and if prepend // characters were to be width 0 they would disappear from the output with // Ghostty's current width 0 handling. Future work will take advantage of // the new uucode `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split. for (0..config.num_code_points) |i| { if (zero_in_grapheme[i] and !is_emoji_modifier[i] and grapheme_break_no_control[i] != .prepend) { items[i] = 0; } else { items[i] = @min(2, standalone[i]); } } } }; const IsSymbolComponent = struct { pub fn build( comptime InputRow: type, comptime Row: type, allocator: std.mem.Allocator, io: std.Io, inputs: config.MultiSlice(InputRow), rows: *config.MultiSlice(Row), backing: anytype, tracking: anytype, ) !void { _ = allocator; _ = io; _ = backing; _ = tracking; rows.len = config.num_code_points; const items = rows.items(.is_symbol); const block = inputs.items(.block); const general_category = inputs.items(.general_category); for (0..config.num_code_points) |i| { items[i] = general_category[i] == .other_private_use or block[i] == .arrows or block[i] == .dingbats or block[i] == .emoticons or block[i] == .miscellaneous_symbols or block[i] == .enclosed_alphanumerics or block[i] == .enclosed_alphanumeric_supplement or block[i] == .miscellaneous_symbols_and_pictographs or block[i] == .transport_and_map_symbols; } } };