diff --git a/build.zig.zon b/build.zig.zon index 7c686cecc..f24871fd6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -39,8 +39,8 @@ }, .uucode = .{ // TODO: currently the use-llvm branch because its broken on self-hosted - .url = "https://deps.files.ghostty.org/uucode-f81f8ef8518b8ec5a7fca30ec5fdbc76cc6197df.tar.gz", - .hash = "uucode-0.1.0-ZZjBPjQHQADuCy1VMWftjrMl3iWqgMpUugWVQJG6_7xT", + .url = "https://github.com/jacobsandlund/uucode/archive/ca307fdeb7eca5c2812b288cdd5650e66b3115eb.tar.gz", + .hash = "uucode-0.1.0-ZZjBPkxTQwCphlTjg-UtY_TzztJy_TZqDL-S2nymmBXY", }, .zig_wayland = .{ // codeberg ifreund/zig-wayland diff --git a/build.zig.zon.json b/build.zig.zon.json index ad0edaa50..526109532 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -114,10 +114,10 @@ "url": "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732", "hash": "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8=" }, - "uucode-0.1.0-ZZjBPjQHQADuCy1VMWftjrMl3iWqgMpUugWVQJG6_7xT": { + "uucode-0.1.0-ZZjBPkxTQwCphlTjg-UtY_TzztJy_TZqDL-S2nymmBXY": { "name": "uucode", - "url": "https://deps.files.ghostty.org/uucode-f81f8ef8518b8ec5a7fca30ec5fdbc76cc6197df.tar.gz", - "hash": "sha256-VomSYOF8fRJwb/8GtVG/QqR6c95zSkQt4649C/4KXAc=" + "url": "https://github.com/jacobsandlund/uucode/archive/ca307fdeb7eca5c2812b288cdd5650e66b3115eb.tar.gz", + "hash": "sha256-AY1JqW7qrWy1+WrlGzL8rHW7mZA6CmYhJVr1sSg19oI=" }, "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS": { "name": "vaxis", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index ccd6285d9..ab51e34dc 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -267,11 +267,11 @@ in }; } { - name = "uucode-0.1.0-ZZjBPjQHQADuCy1VMWftjrMl3iWqgMpUugWVQJG6_7xT"; + name = "uucode-0.1.0-ZZjBPkxTQwCphlTjg-UtY_TzztJy_TZqDL-S2nymmBXY"; path = fetchZigArtifact { name = "uucode"; - url = "https://deps.files.ghostty.org/uucode-f81f8ef8518b8ec5a7fca30ec5fdbc76cc6197df.tar.gz"; - hash = "sha256-VomSYOF8fRJwb/8GtVG/QqR6c95zSkQt4649C/4KXAc="; + url = "https://github.com/jacobsandlund/uucode/archive/ca307fdeb7eca5c2812b288cdd5650e66b3115eb.tar.gz"; + hash = "sha256-AY1JqW7qrWy1+WrlGzL8rHW7mZA6CmYhJVr1sSg19oI="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 8abc4fb7d..f206259cf 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -20,7 +20,6 @@ https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz -https://deps.files.ghostty.org/uucode-f81f8ef8518b8ec5a7fca30ec5fdbc76cc6197df.tar.gz https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz @@ -29,6 +28,7 @@ https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz +https://github.com/jacobsandlund/uucode/archive/ca307fdeb7eca5c2812b288cdd5650e66b3115eb.tar.gz https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251027-150540-8f50c1d/ghostty-themes.tgz https://github.com/natecraddock/zf/archive/3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz https://github.com/rockorager/libvaxis/archive/7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 2a1e38986..8a39a0ccc 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -139,9 +139,9 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/uucode-f81f8ef8518b8ec5a7fca30ec5fdbc76cc6197df.tar.gz", - "dest": "vendor/p/uucode-0.1.0-ZZjBPjQHQADuCy1VMWftjrMl3iWqgMpUugWVQJG6_7xT", - "sha256": "56899260e17c7d12706fff06b551bf42a47a73de734a442de3ae3d0bfe0a5c07" + "url": "https://github.com/jacobsandlund/uucode/archive/ca307fdeb7eca5c2812b288cdd5650e66b3115eb.tar.gz", + "dest": "vendor/p/uucode-0.1.0-ZZjBPkxTQwCphlTjg-UtY_TzztJy_TZqDL-S2nymmBXY", + "sha256": "018d49a96eeaad6cb5f96ae51b32fcac75bb99903a0a6621255af5b12835f682" }, { "type": "archive", diff --git a/src/build/uucode_config.zig b/src/build/uucode_config.zig index 9a3b4bec7..d9e4cb4a3 100644 --- a/src/build/uucode_config.zig +++ b/src/build/uucode_config.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const config = @import("config.zig"); const config_x = @import("config.x.zig"); const d = config.default; @@ -17,11 +18,25 @@ fn computeWidth( _ = cp; _ = backing; _ = tracking; + + // Emoji modifiers are technically width 0 because they're joining + // points. But we handle joining via grapheme break and don't use width + // there. If a emoji modifier is standalone, we want it to take up + // two columns. + if (data.is_emoji_modifier) { + assert(data.wcwidth == 0); + data.wcwidth = 2; + return; + } + data.width = @intCast(@min(2, @max(0, data.wcwidth))); } const width = config.Extension{ - .inputs = &.{"wcwidth"}, + .inputs = &.{ + "is_emoji_modifier", + "wcwidth", + }, .compute = &computeWidth, .fields = &.{ .{ .name = "width", .type = u2 }, diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 64cda5ee3..fb3f458f7 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -3330,6 +3330,35 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { } } +test "Terminal: Fitzpatrick skin tone next valid base" { + var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 }); + defer t.deinit(testing.allocator); + + // Enable grapheme clustering + t.modes.set(.grapheme_cluster, true); + + // This is: "👋🏿" (waving hand with dark skin tone) + try t.print(0x1F44B); // 👋 Waving hand (valid base) + try t.print(0x1F3FF); // 🏿 Dark skin tone modifier + + // The skin tone should combine with the base emoji into a single grapheme cluster, + // taking 2 cells (wide character). + try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + + // Row should be dirty + try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); + + // The base emoji should be in cell 0 with the skin tone as a grapheme + { + const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const cell = list_cell.cell; + try testing.expectEqual(@as(u21, 0x1F44B), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); + try testing.expectEqual(Cell.Wide.wide, cell.wide); + } +} + test "Terminal: Fitzpatrick skin tone next to non-base" { var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 }); defer t.deinit(testing.allocator);