diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 0cabb01f38..9435b3b684 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -7280,7 +7280,7 @@ mod tests { user_message_editor.update_in(cx, |_editor, window, cx| { window.dispatch_action(Box::new(zed_actions::agent::Chat), cx); }); - cx.update(|window, _cx| window.blur()); + cx.update(|window, cx| window.blur(cx)); cx.run_until_parked(); workspace.update_in(cx, |workspace, window, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fa6e418783..da35ae9644 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -11833,7 +11833,7 @@ mod tests { ); // Blur the editor so that it displays placeholder text. - window.blur(); + window.blur(cx); }) .unwrap(); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7182f96714..80e9f739bf 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1764,9 +1764,9 @@ impl App { if focus.ref_count.load(SeqCst) == 0 { for window_handle in self.windows() { window_handle - .update(self, |_, window, _| { + .update(self, |_, window, cx| { if window.focus == Some(handle_id) { - window.blur(); + window.blur(cx); } }) .unwrap(); @@ -2390,10 +2390,7 @@ impl App { for window in self.windows() { window .update(self, |_, window, cx| { - if window.pending_input_keystrokes().is_some() { - window.clear_pending_keystrokes(); - window.pending_input_changed(cx); - } + window.clear_pending_keystrokes(cx); }) .ok(); } diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index 70ac06e659..b3b5dd0914 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -338,7 +338,7 @@ mod tests { assert_eq!(test_window.text_input_configurations().len(), 3); // Losing focus reverts the platform to the default configuration. - cx.update_window(window, |_, window, _| window.blur()) + cx.update_window(window, |_, window, cx| window.blur(cx)) .unwrap(); draw(cx); assert_eq!( diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 7b18d41304..5a037eb1a6 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -830,7 +830,7 @@ mod tests { } #[crate::test] - fn test_pending_input_observers_notified_on_focus_change(cx: &mut TestAppContext) { + fn test_pending_input_observers_notified_on_focus_change_and_blur(cx: &mut TestAppContext) { #[derive(Clone)] struct CustomElement { focus_handle: FocusHandle, @@ -988,6 +988,8 @@ mod tests { cx.update(|cx| { cx.bind_keys([KeyBinding::new("ctrl-b", TestAction, Some("Terminal"))]); cx.bind_keys([KeyBinding::new("ctrl-b h", TestAction, Some("Terminal"))]); + cx.bind_keys([KeyBinding::new("ctrl-d", TestAction, None)]); + cx.bind_keys([KeyBinding::new("ctrl-d h", TestAction, None)]); }); let (test, cx) = cx.add_window_view(|_, cx| CustomElement::new(cx)); @@ -1034,6 +1036,30 @@ mod tests { let count_after_focus_change = *pending_input_changed_count.borrow(); assert!(count_after_focus_change > *count_after_pending_for_assertion.borrow()); }); + + cx.update(|window, cx| window.focus(&focus_handle, cx)); + cx.simulate_keystrokes("ctrl-b"); + let count_before_blur = *pending_input_changed_count.borrow(); + + cx.update(|window, cx| { + assert!(window.has_pending_keystrokes()); + window.blur(cx); + assert!(!window.has_pending_keystrokes()); + assert!(window.pending_input_is_none()); + }); + + cx.update(|_, _| { + assert!(*pending_input_changed_count.borrow() > count_before_blur); + }); + + cx.update(|window, cx| window.disable_focus(cx)); + cx.simulate_keystrokes("ctrl-d"); + + cx.update(|window, cx| { + assert!(window.has_pending_keystrokes()); + window.blur(cx); + assert!(window.pending_input_is_none()); + }); } #[crate::test] @@ -1226,10 +1252,10 @@ mod tests { let prefers_ime_after_blur = { let mut platform_window = cx.test_window(cx.window_handle()); let mut input_handler = platform_window.take_input_handler(); - cx.update(|window, _| { - window.blur(); + cx.update(|window, cx| { + window.blur(cx); assert!(!window.has_pending_keystrokes()); - assert!(window.pending_input_keystrokes().is_none()); + assert!(window.pending_input_is_none()); }); let prefers_ime = input_handler .as_mut() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9656cd167a..acc79fabb5 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2059,24 +2059,15 @@ impl Window { self.focus = Some(handle.id); self.focus_generation = self.focus_generation.wrapping_add(1); - self.clear_pending_keystrokes(); - - // Avoid re-entrant entity updates by deferring observer notifications to the end of the - // current effect cycle, and only for this window. - let window_handle = self.handle; - cx.defer(move |cx| { - window_handle - .update(cx, |_, window, cx| { - window.pending_input_changed(cx); - }) - .ok(); - }); + self.clear_pending_keystrokes(cx); self.refresh(); } /// Remove focus from all elements within this context's window. - pub fn blur(&mut self) { + pub fn blur(&mut self, cx: &mut App) { + self.clear_pending_keystrokes(cx); + if !self.focus_enabled { return; } @@ -2089,8 +2080,8 @@ impl Window { } /// Blur the window and don't allow anything in it to be focused again. - pub fn disable_focus(&mut self) { - self.blur(); + pub fn disable_focus(&mut self, cx: &mut App) { + self.blur(cx); self.focus_enabled = false; } @@ -5458,6 +5449,19 @@ impl Window { .retain(&(), |callback| callback(self, cx)); } + fn defer_pending_input_changed(&self, cx: &mut App) { + // Avoid re-entrant entity updates by deferring observer notifications to the end of the + // current effect cycle, and only for this window. + let window_handle = self.handle; + cx.defer(move |cx| { + window_handle + .update(cx, |_, window, cx| { + window.pending_input_changed(cx); + }) + .ok(); + }); + } + fn dispatch_key_down_up_event( &mut self, event: &dyn Any, @@ -5522,8 +5526,15 @@ impl Window { self.active_pending_input().is_some() } - pub(crate) fn clear_pending_keystrokes(&mut self) { - self.pending_input.take(); + #[cfg(test)] + pub(crate) fn pending_input_is_none(&self) -> bool { + self.pending_input.is_none() + } + + pub(crate) fn clear_pending_keystrokes(&mut self, cx: &mut App) { + if self.pending_input.take().is_some() { + self.defer_pending_input_changed(cx); + } } /// Returns the currently pending input keystrokes that might result in a multi-stroke key binding. @@ -6181,7 +6192,7 @@ impl Window { } } accesskit::Action::Blur => { - self.blur(); + self.blur(cx); } _ => { log::debug!( diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 74eb016d4a..426430619f 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -984,8 +984,8 @@ impl Render for ImageViewToolbarControls { }) .child(editor.clone()) .on_action::({ - move |_: &menu::Confirm, window, _| { - window.blur(); + move |_: &menu::Confirm, window, cx| { + window.blur(cx); } }) .on_action(cx.listener(|this, _: &menu::Cancel, _, cx| { diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 2ee38f5b9e..71de20401e 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -1103,7 +1103,7 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) { ); // Dismiss the rename editor when it loses focus. - workspace.update_in(cx, |_, window, _| window.blur()); + workspace.update_in(cx, |_, window, cx| window.blur(cx)); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), &[ @@ -6869,7 +6869,7 @@ async fn test_selection_restored_when_creation_cancelled(cx: &mut gpui::TestAppC " > test" ] ); - workspace.update_in(cx, |_, window, _| window.blur()); + workspace.update_in(cx, |_, window, cx| window.blur(cx)); cx.executor().run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), diff --git a/crates/settings_ui/src/components/number_field.rs b/crates/settings_ui/src/components/number_field.rs index 614a0830c7..60ca9583a0 100644 --- a/crates/settings_ui/src/components/number_field.rs +++ b/crates/settings_ui/src/components/number_field.rs @@ -800,8 +800,8 @@ impl RenderOnce for NumberField { }) .child(editor) .on_action::({ - move |_, window, _| { - window.blur(); + move |_, window, cx| { + window.blur(cx); } }) .into_any_element() diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 2bc99086ad..a7aa0adea5 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -1455,8 +1455,8 @@ async fn test_keyboard_focus_in_does_not_set_selection(cx: &mut TestAppContext) sidebar.selection = Some(0); }); - cx.update(|window, _cx| { - window.blur(); + cx.update(|window, cx| { + window.blur(cx); }); cx.run_until_parked(); diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 0fa0439379..6d5bbdbc11 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -79,7 +79,7 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) { // Selections aren't changed if editor is blurred but vim-mode is still disabled. cx.cx.set_state("«hjklˇ»"); cx.assert_editor_state("«hjklˇ»"); - cx.update_editor(|_, window, _cx| window.blur()); + cx.update_editor(|_, window, cx| window.blur(cx)); cx.assert_editor_state("«hjklˇ»"); cx.update_editor(|_, window, cx| cx.focus_self(window)); cx.assert_editor_state("«hjklˇ»"); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 63379ed6b0..b580481f43 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -13190,7 +13190,7 @@ mod tests { item.entry_id = None; }); item.is_dirty = true; - window.blur(); + window.blur(cx); }); cx.run_until_parked(); item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); @@ -16001,7 +16001,7 @@ mod tests { assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx)); }); - cx.update(|window, _| window.blur()); + cx.update(|window, cx| window.blur(cx)); cx.executor().run_until_parked(); workspace.update(cx, |_, cx| cx.notify()); cx.executor().run_until_parked();