M3: preserve IME composition in command search (#1252) (#1638)

Co-authored-by: Codex <[email protected]>
This commit is contained in:
FredAppsandCodex authored and GitHub committed 2026-10-13 00:16:26 -04:00
1 parent da9cefa33c
commit 03d9900990
2 files changed
+79 -3

No files matched your search

+5 -3
View File
@@ -159,15 +159,17 @@ pub fn show(app: &mut PdfCraftApp, ctx: &egui::Context) {
.font(theme::regular(15.0))
.desired_width(f32::INFINITY),
);
// Enter runs the top hit. The field keeps focus (requested every frame), so
// check while it is focused as well as when focus is lost.
// Enter runs the selected hit while the field is focused or relinquishing focus.
if (r.has_focus() || r.lost_focus())
&& ui.input(|i| i.key_pressed(egui::Key::Enter))
&& let Some((_, h)) = hits.get(selected)
{
chosen = Some((h.command, h.group));
}
r.request_focus();
// Re-requesting existing focus interrupts native IME composition.
if !r.has_focus() {
r.request_focus();
}
});
ui.separator();
for (index, (_, h)) in hits.iter().enumerate() {
+74
View File
@@ -506,3 +506,77 @@ fn command_w_still_closes_the_document_tab_on_macos() {
h.run_steps(2);
assert!(h.state().views.is_empty(), "⌘W closes the clean document tab");
}
fn palette_ime_output_requests_interrupt(h: &Harness<'_, PdfCraftApp>, phase: &str) -> bool {
assert!(h.state().palette_open, "palette closed during {phase}");
let ime = h.output().platform_output.ime.as_ref().expect("focused palette advertises IME");
eprintln!("{phase}: query={:?}; should_interrupt_composition={}", h.state().palette_query, ime.should_interrupt_composition);
ime.should_interrupt_composition
}
fn palette_ime_output_is_uninterrupted(h: &Harness<'_, PdfCraftApp>, phase: &str) {
assert!(!palette_ime_output_requests_interrupt(h, phase), "palette interrupted composition during {phase}");
}
#[test]
fn palette_ime_idle_frames_and_reopen_do_not_interrupt_composition() {
let mut h = harness();
for opening in 0..2 {
h.state_mut().run_command("view.palette");
h.run_steps(3);
for frame in 0..3 {
h.step();
palette_ime_output_is_uninterrupted(&h, &format!("opening {opening}, idle {frame}"));
}
h.key_press(Key::Escape);
h.run_steps(2);
assert!(!h.state().palette_open);
}
}
#[test]
fn palette_ime_chinese_preedit_commit_and_cancel_remain_uninterrupted() {
let mut h = harness();
let mut interruptions = Vec::new();
h.state_mut().run_command("view.palette");
h.run_steps(3);
for preedit in ["ni", "nih"] {
h.event(egui::Event::Ime(egui::ImeEvent::Preedit { text: preedit.into(), active_range_chars: Some(0..preedit.chars().count()) }));
h.step();
interruptions.push(palette_ime_output_requests_interrupt(&h, preedit));
h.step();
interruptions.push(palette_ime_output_requests_interrupt(&h, "preedit idle"));
}
h.event(egui::Event::Ime(egui::ImeEvent::Commit("你".into())));
h.step();
assert_eq!(h.state().palette_query, "你");
interruptions.push(palette_ime_output_requests_interrupt(&h, "Chinese commit"));
h.run_steps(2);
assert_eq!(h.state().palette_query, "你");
interruptions.push(palette_ime_output_requests_interrupt(&h, "committed idle"));
h.event(egui::Event::Ime(egui::ImeEvent::Preedit { text: "hao".into(), active_range_chars: Some(0..3) }));
h.step();
interruptions.push(palette_ime_output_requests_interrupt(&h, "second preedit"));
h.event(egui::Event::Ime(egui::ImeEvent::Preedit { text: String::new(), active_range_chars: None }));
h.step();
assert_eq!(h.state().palette_query, "你");
interruptions.push(palette_ime_output_requests_interrupt(&h, "cancel preedit"));
assert!(interruptions.iter().all(|interrupt| !interrupt), "composition interrupted: {interruptions:?}");
}
#[test]
fn palette_ime_outside_click_closes_without_running_a_command() {
let mut h = harness();
h.state_mut().set_option("palette", "rotate pages clockwise").unwrap();
h.run_steps(3);
h.event(egui::Event::PointerButton {
pos: egui::pos2(20.0, 850.0),
button: egui::PointerButton::Primary,
pressed: true,
modifiers: Modifiers::NONE,
});
h.step();
assert!(!h.state().palette_open);
assert!(h.state().palette_query.is_empty());
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().info.pages[0].rotation, 0);
}