From 23195886538ad33d362e0e8ec049372eb68091a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=BA=E4=B8=8D=E5=8F=AB=E5=8C=96=E5=92=95=E9=BE=99?= <1801943622@qq.com> Date: Fri, 22 May 2026 21:44:54 +0800 Subject: [PATCH 1/4] Forget IME transactions --- crates/editor/src/input.rs | 10 +++++++++- crates/vim/src/test.rs | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/input.rs b/crates/editor/src/input.rs index afd0850c6ad..dc48eaecf4a 100644 --- a/crates/editor/src/input.rs +++ b/crates/editor/src/input.rs @@ -1827,7 +1827,15 @@ impl Editor { .text_highlights(HighlightKey::PendingInput, cx) .is_none() { - self.ime_transaction.take(); + let ime_tx = self.ime_transaction.take(); + self.buffer().update(cx, |buffer, cx| { + if let Some(transaction) = ime_tx { + buffer.forget_transaction(transaction, cx); + } + if let Some(tx) = transaction { + buffer.forget_transaction(tx, cx); + } + }); } } diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 99eda3d6c48..2a68323018a 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1402,6 +1402,30 @@ async fn test_undo(cx: &mut gpui::TestAppContext) { 3"}); } +#[perf] +#[gpui::test] +async fn test_ime_transaction_undo(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.update(|_, cx| { + cx.bind_keys([KeyBinding::new( + "j k", + NormalBefore, + Some("vim_mode == insert"), + )]) + }); + + cx.set_state("ˇone", Mode::Normal); + cx.simulate_keystrokes("i j"); + cx.assert_state("ˇjone", Mode::Insert); + assert_pending_input(&mut cx, "«j»one"); + cx.simulate_keystrokes("k"); + cx.assert_state("ˇone", Mode::Normal); + + cx.simulate_keystrokes("u"); + cx.assert_state("ˇ", Mode::Normal); +} + #[perf] #[gpui::test] async fn test_lsp_completions_undo(cx: &mut gpui::TestAppContext) { From a36befdaf5d7d2364b72e43baf66a3dd84adda04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=BA=E4=B8=8D=E5=8F=AB=E5=8C=96=E5=92=95=E9=BE=99?= <1801943622@qq.com> Date: Sat, 23 May 2026 01:17:02 +0800 Subject: [PATCH 2/4] Notify when pending input is cleared --- crates/gpui/src/window.rs | 6 ++++++ crates/vim/src/test.rs | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f5292840299..96c5fcb2fe8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4687,6 +4687,7 @@ impl Window { } let mut currently_pending = self.pending_input.take().unwrap_or_default(); + let had_pending_input = !currently_pending.keystrokes.is_empty(); if currently_pending.focus.is_some() && currently_pending.focus != self.focus { currently_pending = PendingInput::default(); } @@ -4697,6 +4698,11 @@ impl Window { &dispatch_path, ); + let pending_input_was_cleared = had_pending_input && match_result.pending.is_empty(); + if pending_input_was_cleared { + self.pending_input_changed(cx); + } + if !match_result.to_replay.is_empty() { self.replay_pending_input(match_result.to_replay, cx); cx.propagate_event = true; diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 2a68323018a..a783b079cb5 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1426,6 +1426,27 @@ async fn test_ime_transaction_undo(cx: &mut gpui::TestAppContext) { cx.assert_state("ˇ", Mode::Normal); } +#[perf] +#[gpui::test] +async fn test_pending_input_mapping_output_undo(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.update(|_, cx| { + cx.bind_keys([KeyBinding::new( + "a b c", + workspace::SendKeystrokes("d".to_string()), + Some("vim_mode == insert"), + )]) + }); + + cx.set_state("ˇone", Mode::Normal); + cx.simulate_keystrokes("i a b c"); + cx.assert_state("dˇone", Mode::Insert); + + cx.simulate_keystrokes("escape u"); + cx.assert_state("ˇone", Mode::Normal); +} + #[perf] #[gpui::test] async fn test_lsp_completions_undo(cx: &mut gpui::TestAppContext) { From a71c3c867e01bbf8bb6777db20276141027c3f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=BA=E4=B8=8D=E5=8F=AB=E5=8C=96=E5=92=95=E9=BE=99?= <1801943622@qq.com> Date: Sat, 23 May 2026 17:10:50 +0800 Subject: [PATCH 3/4] Change the test to use synchronous editor input instead --- crates/gpui/src/window.rs | 9 +++++++-- crates/vim/src/test.rs | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 96c5fcb2fe8..036f3effccc 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4700,6 +4700,7 @@ impl Window { let pending_input_was_cleared = had_pending_input && match_result.pending.is_empty(); if pending_input_was_cleared { + // Synchronous binding actions may edit immediately, so clear pending input first. self.pending_input_changed(cx); } @@ -4787,14 +4788,18 @@ impl Window { match_result.context_stack, cx, ); - self.pending_input_changed(cx); + if !pending_input_was_cleared { + self.pending_input_changed(cx); + } return; } } } self.finish_dispatch_key_event(event, dispatch_path, match_result.context_stack, cx); - self.pending_input_changed(cx); + if !pending_input_was_cleared { + self.pending_input_changed(cx); + } } fn finish_dispatch_key_event( diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index a783b079cb5..df6fac20aca 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -8,7 +8,7 @@ use collections::HashMap; use command_palette::CommandPalette; use editor::{ AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, MultiBufferOffset, - actions::{DeleteLine, WrapSelectionsInTag}, + actions::{DeleteLine, HandleInput, WrapSelectionsInTag}, code_context_menus::CodeContextMenu, display_map::DisplayRow, test::editor_test_context::EditorTestContext, @@ -1434,7 +1434,7 @@ async fn test_pending_input_mapping_output_undo(cx: &mut gpui::TestAppContext) { cx.update(|_, cx| { cx.bind_keys([KeyBinding::new( "a b c", - workspace::SendKeystrokes("d".to_string()), + HandleInput("d".to_string()), Some("vim_mode == insert"), )]) }); From b7b8c03c51e5f4dc44d0d2bbf3be420be6114e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=BA=E4=B8=8D=E5=8F=AB=E5=8C=96=E5=92=95=E9=BE=99?= <1801943622@qq.com> Date: Fri, 29 May 2026 21:01:02 +0800 Subject: [PATCH 4/4] Add timeout testcase --- crates/vim/src/test.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index df6fac20aca..ab8f75fcb9e 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1447,6 +1447,29 @@ async fn test_pending_input_mapping_output_undo(cx: &mut gpui::TestAppContext) { cx.assert_state("ˇone", Mode::Normal); } +#[perf] +#[gpui::test] +async fn test_pending_input_mapping_output_undo_delay(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.update(|_, cx| { + cx.bind_keys([KeyBinding::new( + "a b c", + HandleInput("d".to_string()), + Some("vim_mode == insert"), + )]) + }); + + cx.set_state("ˇone", Mode::Normal); + cx.simulate_keystrokes("i a b"); + cx.executor().advance_clock(Duration::from_millis(1500)); + cx.run_until_parked(); + cx.assert_state("abˇone", Mode::Insert); + + cx.simulate_keystrokes("escape u"); + cx.assert_state("ˇone", Mode::Normal); +} + #[perf] #[gpui::test] async fn test_lsp_completions_undo(cx: &mut gpui::TestAppContext) {