![]() ## Description This PR implements basic support for Japanese Input Method Editors (IMEs) in the Zed terminal on macOS, addressing issue #9900. Previously, users had to switch input modes to confirm Japanese text, and pre-edit (marked) text was not displayed. With these changes: - **Marked Text Display:** Pre-edit text (e.g., underlined characters during Japanese composition) is now rendered directly in the terminal at the cursor's current position. - **Composition Confirmation:** Pressing Enter correctly finalizes the IME composition, clears the marked text, and sends the confirmed string to the underlying PTY process. This allows for a more natural input flow similar to other macOS applications like iTerm2. - **State Management:** IME state (marked text and its selected range within the marked text) is now managed within the `TerminalView` struct. - **Input Handling:** `TerminalInputHandler` has been updated to correctly process IME callbacks (`replace_and_mark_text_in_range`, `replace_text_in_range`, `unmark_text`, `marked_text_range`) by interacting with `TerminalView`. - **Painting Logic:** `TerminalElement::paint` now fetches the marked text and its range from `TerminalView` and renders it with an underline. The standard terminal cursor is hidden when marked text is present to avoid visual clutter. - **Candidate Window Positioning:** `TerminalInputHandler::bounds_for_range` now attempts to provide more accurate bounds for the IME candidate window by using the actual painted bounds of the pre-edit text, falling back to a cursor-based approximation if necessary. This significantly improves the usability of the Zed terminal for users who need to input Japanese characters, bringing the experience closer to system-standard IME behavior. ## Movies https://github.com/user-attachments/assets/be6c7597-7b65-49a6-b376-e1adff6da974 --- Closes #9900 Release Notes: - **Terminal:** Implemented basic support for Japanese Input Method Editors (IMEs) on macOS. Users can now see pre-edit (marked) text as they type Japanese and confirm their input with the Enter key directly in the terminal. This provides a more natural and efficient experience for Japanese language input. (Fixes #9900) --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> |
||
---|---|---|
.. | ||
scripts | ||
src | ||
Cargo.toml | ||
LICENSE-GPL | ||
README.md |
Design notes:
This crate is split into two conceptual halves:
- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
- Everything else. These other files integrate the
Terminal
struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file.
ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. TerminalBuilder
solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with TerminalBuilder::new()
, check the result, then call TerminalBuilder::subscribe(cx)
from within a model context.
The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors.
#Input
There are currently many distinct paths for getting keystrokes to the terminal:
-
Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the
try_keystroke()
method on Terminal -
GPU Action handlers. GPUI clobbers a few vital keys by adding bindings to them in the global context. These keys are synthesized and then dispatched through the same
try_keystroke()
API as the above mappings -
IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the
View::replace_text_in_range()
method, and we then send that to the terminal directly, bypassingtry_keystroke()
. -
Pasted text has a separate pathway.
Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the .input()
API (which try_keystroke uses) so we have consistent input handling across the terminal