This PR updates the logic in Terminal `print` to include more cases of
changing a cell to be wide due to a grapheme cluster that needs to be
wide but starts off narrow. The existing case of this is a
text-presentation code point followed by VS16 to make it emoji
presentation. This PR handles more cases that are found in scripts such
as Devanagari where the correct grapheme width calculation sums up
multiple code points of non-zero widths. An example, as seen from
[uucode's issue #1](https://github.com/jacobsandlund/uucode/issues/1) is
`क्ष`, which now with https://github.com/ghostty-org/ghostty/pull/9680
merged is one grapheme cluster instead of two, but the U+0915 (first
code point) is width one and U+0937 (final code point) is also width
one, and the whole cluster should be width 1 + 1 = 2. This is important
to address with the grapheme break change otherwise these scripts would
show with narrow cells, incorrectly.
Before:
<img width="680" height="124" alt="CleanShot 2026-01-27 at 10 31 24@2x"
src="https://github.com/user-attachments/assets/4ff5959d-9c14-4062-8280-83004af38495"
/>
After:
<img width="646" height="118" alt="CleanShot 2026-01-27 at 10 29 10@2x"
src="https://github.com/user-attachments/assets/3ad11afd-2141-46fb-b22b-9fa7b2546366"
/>
---
Note that the logic here just takes `width_zero_in_grapheme` and if it's
not zero width, makes the cell wide. This is actually wrong for
graphemes with `prepend` (usually/always? zero width) followed by a
character that should be narrow width, but that's affecting a much
smaller number of graphemes. To address that, we would need to run the
full `wcwidth` from `uucode` on the grapheme, and compare the width
output with the current cell's `Wide`. I figured it'd be better to
incrementally just handle the bulk of the cases with the
`width_zero_in_grapheme` check.
This also adds tests to make sure moving the cell is handled correctly,
which was not the case for the existing VS16 logic.
There's a lot of code here to handle transferring the graphemes when the
narrow cell should wrap to the next line to become wide. I'd like
feedback on the approach here before attempting to clean anything up, if
desired (pull it out into a separate method?).
AI was used in some of the uucode changes in
https://github.com/ghostty-org/ghostty/pull/9678 (Amp--primarily for
tests), but everything was carefully vetted and much of it done by hand.
This PR was made without AI.
* Use a GitHub action to download the Android NDK
* Use helper functions available on `std.Build` to simplify the build
script.
* Use various Zig-isms to simplify the code.
FYI, using Nix to seems to be a non-starter as getting any Android
development kits from nixpkgs requires accepting the Android license
agreement and allowing many packages to use unfree licenses. And since
the packages are unfree they are not cached by NixOS so the build
triggers massive memory-hungry builds.
* Use a GitHub action to download the Android NDK
* Use helper functions available on `std.Build` to simplify
the build script.
* Use various Zig-isms to simplify the code.
FYI, using Nix to seems to be a non-starter as getting any Android
development kits from nixpkgs requires accepting the Android license
agreement and allowing many packages to use unfree licenses. And since
the packages are unfree they are not cached by NixOS so the build
triggers massive memory-hungry builds.
The PR introduces `lib-vt` Android support as discussed in #10902.
A few more notes:
- Introduces new CI for Android builds as a change requires NDK to be
configured.
- To build locally, it is required to have the NDK installed in the
system and either have the path exported via `ANDROID_NDK_HOME` pointing
to the exact NDK path or `ANDROID_HOME` or `ANDROID_SDK_ROOT` pointing
at the Android SDK path from which the build system will infer the NDK
path and version.
- 16kb page size alignment is configured for Android 15+. Builds are
backward compatible with 4kb page size devices.
Fixes#10548
Escaped characters in selection-word-chars are now correctly parsed,
allowing for characters like `\t` to be included in the set of word
characters.
Fixes#10548
Escaped characters in selection-word-chars are now correctly parsed,
allowing for characters like `\t` to be included in the set of word
characters.
Fixes#10680
The image state is used for drawing, so when we update it, we need to
acquire the draw mutex. All our other state updates already acquire the
draw mutex but Kitty images are odd in that they happen in the critical
area (due to their size).
Fixes#10680
The image state is used for drawing, so when we update it, we need to
acquire the draw mutex. All our other state updates already acquire the
draw mutex but Kitty images are odd in that they happen in the critical
area (due to their size).
## Summary
Cmd-clicking a file path containing `~` (e.g. `~/Documents/file.txt`)
fails to open the file on macOS because `URL(filePath:)` treats `~` as a
literal directory name rather than the user's home directory.
This uses `NSString.expandingTildeInPath` to resolve `~` before
constructing the file URL.
## Root Cause
In `openURL()`, when the URL string has no scheme it falls through to:
```swift
url = URL(filePath: action.url)
```
Swift's `URL(filePath:)` does not perform tilde expansion. A path like
`~/Documents/file.txt` produces a URL pointing to a non-existent file,
and `NSWorkspace.open` silently fails.
## Fix
```swift
let expandedPath = NSString(string: action.url).expandingTildeInPath
url = URL(filePath: expandedPath)
```
## Reproduction
1. Have a terminal application (e.g. Claude Code) that outputs file
paths with `~` prefixes
2. Cmd-click the path in Ghostty on macOS
3. The file does not open (fails silently)
With this fix, the path resolves correctly and opens in the default
editor.
`URL(filePath:)` treats `~` as a literal directory name, so
cmd-clicking a path like `~/Documents/file.txt` would fail to
open because the resulting file URL doesn't point to a real file.
Use `NSString.expandingTildeInPath` to resolve `~` to the user's
home directory before constructing the file URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>