Files
ghostty-rs/docs/rewrite/notes/final-Z10.md
T
ddidderr db954441fa fix(input): carry key and preedit text as bytes like Zig
Zig's KeyEvent.utf8 and the embedded API's key/preedit text are byte
slices passed through to key encoding and the pty; the port converted
them lossily to UTF-8.
- KeyEvent.utf8 is &[u8] end to end (ghostty-input encoder, core,
  ghostty-lib C API, libghostty-vt key events, GTK callers). Where Zig
  validates with Utf8View (alternates, modify-other-keys, ctrl CSI-u,
  associated text, binding unicode lookup) invalid text takes the same
  "no UTF-8" fallback.
- Invalid preedit UTF-8 behaves like Zig's `try Utf8View.init`: the
  prior preedit is cleared and marked dirty, the callback returns an
  error (logged by the C API) and no preedit is set.
- The inspector key widget shows invalid key text lossily, display only.

Test Plan:
- cargo test --workspace --no-fail-fast (3407 passed)
- cargo test -p ghostty-lib --features test-platform (C-API key text
  0xff 0xfe reaches the pty unchanged)
- just check-macos (both), just check-windows, just check-wasm
2026-10-12 11:01:17 +02:00

1.9 KiB

Z10: byte-typed key text and preedit

KeyEvent.utf8 is now &[u8] (Zig []const u8) end to end; the Z9 "documented limit" for C key text / preedit is gone.

  • ghostty-input: KeyEvent.utf8: &[u8]; key_encode writes the bytes unchanged. Where Zig validates with Utf8View.init ... catch (plain-text control scan, kitty alternates, modify-other-keys, ctrl CSI-u, kitty associated text, binding unicode lookup) invalid UTF-8 takes the same "no UTF-8" fallback (utf8_chars). Legacy/kitty "send text directly" paths write the raw bytes.
  • ghostty-core: OwnedKeyEvent.utf8: Vec<u8>; Surface::preedit_callback(Option<&[u8]>) (and the RuntimeSurface trait in ghostty-apprt). Preedit bytes are decoded lossily (U+FFFD) for the renderer codepoints only. Deviation: Zig returns an error on invalid preedit; we render replacement chars instead (no panic).
  • ghostty-lib: ghostty_surface_key copies text as raw bytes; ghostty_surface_preedit passes bytes. ghostty_surface_text was already bytes.
  • ghostty-vt-capi: ghostty_key_event_* no longer truncates invalid UTF-8 to its valid prefix; passes bytes through.
  • Inspector key widget shows invalid text lossily (display only). GTK callers pass as_bytes() of valid UTF-8.
  • Tests: ghostty-lib/tests/surface.rs::key_text_and_preedit_are_bytes_not_lossy_utf8 (key text ff fe reaches the pty), ghostty-core/tests/e2e_ime.rs::preedit_with_invalid_utf8_is_replaced_for_display_only, ghostty-input invalid_utf8_* encoder tests. Existing tests only changed mechanically ("x" -> "x".as_bytes()).

Orchestrator follow-up

Preedit with invalid UTF-8 now matches Zig exactly instead of rendering U+FFFD: preedit_callback clears the prior preedit, marks it dirty and returns an error (Zig: try std.unicode.Utf8View.init(text)); the C API logs it like embedded.zig. Test: preedit_with_invalid_utf8_is_an_error_and_clears_prior.