1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Fix backspacing leaving last character in IME prediction not removed on macOS native and Safari (#7810)

<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes N/A
* [x] I have followed the instructions in the PR template

## Before the fix

| Platform | Screenshot |
| - | - |
| macOS native |
![before-macos15-apple_shuangpin](https://github.com/user-attachments/assets/8397b236-7adf-4eca-9eb6-337e42c9efae)
|
| Safari |
![before-safari26](https://github.com/user-attachments/assets/1f4162a2-ccb7-4b42-960d-95aa3310f908)
|

## After the fix

| Platform | Screenshot |
| - | - |
| macOS native |
![after-macos15-apple_shuangpin](https://github.com/user-attachments/assets/8f50d43c-21bc-4c47-a7fb-86d0543c5088)
|
| Safari |
![after-safari26](https://github.com/user-attachments/assets/be4a69cd-8a0e-4512-865b-d6ebed2fd6c7)
|

(The font used in the screenshots is [GNU
Unifont](https://unifoundry.com/unifont/index.html), licensed under
[OFL-1.1.txt](https://unifoundry.com/OFL-1.1.txt).)
This commit is contained in:
Umaĵo
2026-01-05 19:51:09 +08:00
committed by GitHub
parent f9bf0ee6c4
commit 6d416fab2e
2 changed files with 159 additions and 77 deletions

View File

@@ -1065,51 +1065,73 @@ fn events(
..
} => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key),
Event::Ime(ime_event) => match ime_event {
ImeEvent::Enabled => {
state.ime_enabled = true;
state.ime_cursor_range = cursor_range;
None
Event::Ime(ime_event) => {
/// Empty prediction can be produced with [`ImeEvent::Preedit`]
/// or [`ImeEvent::Commit`] when user press backspace or escape
/// during IME, so this function should be called in both cases
/// to clear current text.
///
/// Example platforms where only `ImeEvent::Preedit("")` of
/// those two events is emitted when the last character in the
/// prediction is deleted:
/// - macOS 15.7.3.
/// - Debian13 with gnome48 and wayland.
///
/// An example platform where only `ImeEvent::Commit("")` of
/// those two events is emitted when the last character in the
/// prediction is deleted:
/// - Safari 26.2 (on macOS 15.7.3).
fn clear_prediction(
text: &mut dyn TextBuffer,
cursor_range: &CCursorRange,
) -> CCursor {
text.delete_selected(cursor_range)
}
ImeEvent::Preedit(text_mark) => {
if text_mark == "\n" || text_mark == "\r" {
None
} else {
// Empty prediction can be produced when user press backspace
// or escape during IME, so we clear current text.
let mut ccursor = text.delete_selected(&cursor_range);
let start_cursor = ccursor;
if !text_mark.is_empty() {
text.insert_text_at(&mut ccursor, text_mark, char_limit);
}
state.ime_cursor_range = cursor_range;
Some(CCursorRange::two(start_cursor, ccursor))
}
}
ImeEvent::Commit(prediction) => {
if prediction == "\n" || prediction == "\r" {
None
} else {
state.ime_enabled = false;
if !prediction.is_empty()
&& cursor_range.secondary.index
== state.ime_cursor_range.secondary.index
{
let mut ccursor = text.delete_selected(&cursor_range);
text.insert_text_at(&mut ccursor, prediction, char_limit);
Some(CCursorRange::one(ccursor))
match ime_event {
ImeEvent::Enabled => {
state.ime_enabled = true;
state.ime_cursor_range = cursor_range;
None
}
ImeEvent::Preedit(text_mark) => {
if text_mark == "\n" || text_mark == "\r" {
None
} else {
let ccursor = cursor_range.primary;
let mut ccursor = clear_prediction(text, &cursor_range);
let start_cursor = ccursor;
if !text_mark.is_empty() {
text.insert_text_at(&mut ccursor, text_mark, char_limit);
}
state.ime_cursor_range = cursor_range;
Some(CCursorRange::two(start_cursor, ccursor))
}
}
ImeEvent::Commit(prediction) => {
if prediction == "\n" || prediction == "\r" {
None
} else {
state.ime_enabled = false;
let mut ccursor = clear_prediction(text, &cursor_range);
if !prediction.is_empty()
&& cursor_range.secondary.index
== state.ime_cursor_range.secondary.index
{
text.insert_text_at(&mut ccursor, prediction, char_limit);
}
Some(CCursorRange::one(ccursor))
}
}
ImeEvent::Disabled => {
state.ime_enabled = false;
None
}
}
ImeEvent::Disabled => {
state.ime_enabled = false;
None
}
},
}
_ => None,
};