Fire focus handlers on draw to avoid timing with newly created item (#3640)

Julia created

See title

Any focus changes which fire from within a focus handler should be
handled in the next frame

Release Notes:

- N/A

Change summary

crates/gpui2/src/app.rs                    |  71 +--------
crates/gpui2/src/elements/div.rs           |  13 -
crates/gpui2/src/interactive.rs            |   8 -
crates/gpui2/src/key_dispatch.rs           |  20 ++
crates/gpui2/src/window.rs                 | 163 ++++++++++-------------
crates/project_panel2/src/project_panel.rs |  21 ++
crates/search2/src/project_search.rs       |  32 +++-
crates/workspace2/src/item.rs              |   9 
crates/workspace2/src/workspace2.rs        |   1 
9 files changed, 152 insertions(+), 186 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -17,11 +17,10 @@ use time::UtcOffset;
 use crate::{
     current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
     AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
-    DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
-    ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels,
-    Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription,
-    SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window,
-    WindowContext, WindowHandle, WindowId,
+    DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap,
+    Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
+    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
+    TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{FxHashMap, FxHashSet, VecDeque};
@@ -572,28 +571,27 @@ impl AppContext {
         loop {
             self.release_dropped_entities();
             self.release_dropped_focus_handles();
+
             if let Some(effect) = self.pending_effects.pop_front() {
                 match effect {
                     Effect::Notify { emitter } => {
                         self.apply_notify_effect(emitter);
                     }
+
                     Effect::Emit {
                         emitter,
                         event_type,
                         event,
                     } => self.apply_emit_effect(emitter, event_type, event),
-                    Effect::FocusChanged {
-                        window_handle,
-                        focused,
-                    } => {
-                        self.apply_focus_changed_effect(window_handle, focused);
-                    }
+
                     Effect::Refresh => {
                         self.apply_refresh_effect();
                     }
+
                     Effect::NotifyGlobalObservers { global_type } => {
                         self.apply_notify_global_observers_effect(global_type);
                     }
+
                     Effect::Defer { callback } => {
                         self.apply_defer_effect(callback);
                     }
@@ -613,7 +611,7 @@ impl AppContext {
                     .values()
                     .filter_map(|window| {
                         let window = window.as_ref()?;
-                        window.dirty.then_some(window.handle)
+                        (window.dirty || window.focus_invalidated).then_some(window.handle)
                     })
                     .collect::<Vec<_>>()
                 {
@@ -693,51 +691,6 @@ impl AppContext {
             });
     }
 
-    fn apply_focus_changed_effect(
-        &mut self,
-        window_handle: AnyWindowHandle,
-        focused: Option<FocusId>,
-    ) {
-        window_handle
-            .update(self, |_, cx| {
-                // The window might change focus multiple times in an effect cycle.
-                // We only honor effects for the most recently focused handle.
-                if cx.window.focus == focused {
-                    // if someone calls focus multiple times in one frame with the same handle
-                    // the first apply_focus_changed_effect will have taken the last blur already
-                    // and run the rest of this, so we can return.
-                    let Some(last_blur) = cx.window.last_blur.take() else {
-                        return;
-                    };
-
-                    let focused = focused
-                        .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
-
-                    let blurred =
-                        last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
-
-                    let focus_changed = focused.is_some() || blurred.is_some();
-                    let event = FocusEvent { focused, blurred };
-
-                    let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners);
-                    if focus_changed {
-                        for listener in &mut listeners {
-                            listener(&event, cx);
-                        }
-                    }
-                    cx.window.rendered_frame.focus_listeners = listeners;
-
-                    if focus_changed {
-                        cx.window
-                            .focus_listeners
-                            .clone()
-                            .retain(&(), |listener| listener(&event, cx));
-                    }
-                }
-            })
-            .ok();
-    }
-
     fn apply_refresh_effect(&mut self) {
         for window in self.windows.values_mut() {
             if let Some(window) = window.as_mut() {
@@ -1252,10 +1205,6 @@ pub(crate) enum Effect {
         event_type: TypeId,
         event: Box<dyn Any>,
     },
-    FocusChanged {
-        window_handle: AnyWindowHandle,
-        focused: Option<FocusId>,
-    },
     Refresh,
     NotifyGlobalObservers {
         global_type: TypeId,

crates/gpui2/src/elements/div.rs 🔗

@@ -1,10 +1,9 @@
 use crate::{
     point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
-    BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
-    IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
-    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
-    SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
-    WindowContext,
+    BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement,
+    KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size,
+    StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
 };
 
 use collections::HashMap;
@@ -636,10 +635,6 @@ pub trait FocusableElement: InteractiveElement {
     }
 }
 
-pub type FocusListeners = Vec<FocusListener>;
-
-pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
-
 pub type MouseDownListener =
     Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
 pub type MouseUpListener =

crates/gpui2/src/interactive.rs 🔗

@@ -1,6 +1,5 @@
 use crate::{
-    div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point,
-    Render, ViewContext,
+    div, point, Div, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
 };
 use smallvec::SmallVec;
 use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
@@ -290,11 +289,6 @@ impl InputEvent {
     }
 }
 
-pub struct FocusEvent {
-    pub blurred: Option<FocusHandle>,
-    pub focused: Option<FocusHandle>,
-}
-
 #[cfg(test)]
 mod test {
     use crate::{

crates/gpui2/src/key_dispatch.rs 🔗

@@ -29,6 +29,7 @@ pub(crate) struct DispatchNode {
     pub key_listeners: Vec<KeyListener>,
     pub action_listeners: Vec<DispatchActionListener>,
     pub context: Option<KeyContext>,
+    focus_id: Option<FocusId>,
     parent: Option<DispatchNodeId>,
 }
 
@@ -127,8 +128,9 @@ impl DispatchTree {
     }
 
     pub fn make_focusable(&mut self, focus_id: FocusId) {
-        self.focusable_node_ids
-            .insert(focus_id, self.active_node_id());
+        let node_id = self.active_node_id();
+        self.active_node().focus_id = Some(focus_id);
+        self.focusable_node_ids.insert(focus_id, node_id);
     }
 
     pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
@@ -247,6 +249,20 @@ impl DispatchTree {
         dispatch_path
     }
 
+    pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> {
+        let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new();
+        let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied();
+        while let Some(node_id) = current_node_id {
+            let node = self.node(node_id);
+            if let Some(focus_id) = node.focus_id {
+                focus_path.push(focus_id);
+            }
+            current_node_id = node.parent;
+        }
+        focus_path.reverse(); // Reverse the path so it goes from the root to the focused node.
+        focus_path
+    }
+
     pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode {
         &self.nodes[node_id.0]
     }

crates/gpui2/src/window.rs 🔗

@@ -2,14 +2,14 @@ use crate::{
     key_dispatch::DispatchActionListener, px, size, transparent_black, Action, AnyDrag, AnyView,
     AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners,
     CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
-    EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId,
-    Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent,
-    LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent,
-    MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
-    PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString,
-    Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
-    UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla,
+    ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId,
+    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent,
+    Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style,
+    SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
+    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
 use collections::FxHashMap;
@@ -86,9 +86,13 @@ impl DispatchPhase {
 
 type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
 type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
-type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
 type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
 
+struct FocusEvent {
+    previous_focus_path: SmallVec<[FocusId; 8]>,
+    current_focus_path: SmallVec<[FocusId; 8]>,
+}
+
 slotmap::new_key_type! { pub struct FocusId; }
 
 impl FocusId {
@@ -239,8 +243,8 @@ pub struct Window {
     pub(crate) rendered_frame: Frame,
     pub(crate) next_frame: Frame,
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
-    pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
-    pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>,
+    focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
+    blur_listeners: SubscriberSet<(), AnyObserver>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
     modifiers: Modifiers,
@@ -252,8 +256,10 @@ pub struct Window {
     pub(crate) dirty: bool,
     pub(crate) drawing: bool,
     activation_observers: SubscriberSet<(), AnyObserver>,
-    pub(crate) last_blur: Option<Option<FocusId>>,
     pub(crate) focus: Option<FocusId>,
+
+    #[cfg(any(test, feature = "test-support"))]
+    pub(crate) focus_invalidated: bool,
 }
 
 pub(crate) struct ElementStateBox {
@@ -264,10 +270,10 @@ pub(crate) struct ElementStateBox {
 
 // #[derive(Default)]
 pub(crate) struct Frame {
+    focus: Option<FocusId>,
     pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
     mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
     pub(crate) dispatch_tree: DispatchTree,
-    pub(crate) focus_listeners: Vec<AnyFocusListener>,
     pub(crate) scene_builder: SceneBuilder,
     pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
     pub(crate) z_index_stack: StackingOrder,
@@ -278,10 +284,10 @@ pub(crate) struct Frame {
 impl Frame {
     fn new(dispatch_tree: DispatchTree) -> Self {
         Frame {
+            focus: None,
             element_states: FxHashMap::default(),
             mouse_listeners: FxHashMap::default(),
             dispatch_tree,
-            focus_listeners: Vec::new(),
             scene_builder: SceneBuilder::default(),
             z_index_stack: StackingOrder::default(),
             depth_map: Default::default(),
@@ -293,10 +299,15 @@ impl Frame {
     fn clear(&mut self) {
         self.element_states.clear();
         self.mouse_listeners.values_mut().for_each(Vec::clear);
-        self.focus_listeners.clear();
         self.dispatch_tree.clear();
         self.depth_map.clear();
     }
+
+    fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
+        self.focus
+            .map(|focus_id| self.dispatch_tree.focus_path(focus_id))
+            .unwrap_or_default()
+    }
 }
 
 impl Window {
@@ -389,8 +400,10 @@ impl Window {
             dirty: false,
             drawing: false,
             activation_observers: SubscriberSet::new(),
-            last_blur: None,
             focus: None,
+
+            #[cfg(any(test, feature = "test-support"))]
+            focus_invalidated: false,
         }
     }
 }
@@ -468,35 +481,23 @@ impl<'a> WindowContext<'a> {
             return;
         }
 
-        let focus_id = handle.id;
-
-        if self.window.last_blur.is_none() {
-            self.window.last_blur = Some(self.window.focus);
-        }
-
-        self.window.focus = Some(focus_id);
+        self.window.focus = Some(handle.id);
         self.window
             .rendered_frame
             .dispatch_tree
             .clear_pending_keystrokes();
-        self.app.push_effect(Effect::FocusChanged {
-            window_handle: self.window.handle,
-            focused: Some(focus_id),
-        });
+
+        #[cfg(any(test, feature = "test-support"))]
+        {
+            self.window.focus_invalidated = true;
+        }
+
         self.notify();
     }
 
     /// Remove focus from all elements within this context's window.
     pub fn blur(&mut self) {
-        if self.window.last_blur.is_none() {
-            self.window.last_blur = Some(self.window.focus);
-        }
-
         self.window.focus = None;
-        self.app.push_effect(Effect::FocusChanged {
-            window_handle: self.window.handle,
-            focused: None,
-        });
         self.notify();
     }
 
@@ -1258,16 +1259,11 @@ impl<'a> WindowContext<'a> {
         self.window.dirty = false;
         self.window.drawing = true;
 
-        let window_was_focused = self
-            .window
-            .focus
-            .and_then(|focus_id| {
-                self.window
-                    .rendered_frame
-                    .dispatch_tree
-                    .focusable_node_id(focus_id)
-            })
-            .is_some();
+        #[cfg(any(test, feature = "test-support"))]
+        {
+            self.window.focus_invalidated = false;
+        }
+
         self.text_system().start_frame();
         self.window.platform_window.clear_input_handler();
         self.window.layout_engine.as_mut().unwrap().clear();
@@ -1306,23 +1302,6 @@ impl<'a> WindowContext<'a> {
             });
         }
 
-        let window_is_focused = self
-            .window
-            .focus
-            .and_then(|focus_id| {
-                self.window
-                    .next_frame
-                    .dispatch_tree
-                    .focusable_node_id(focus_id)
-            })
-            .is_some();
-        if window_was_focused && !window_is_focused {
-            self.window
-                .blur_listeners
-                .clone()
-                .retain(&(), |listener| listener(self));
-        }
-
         self.window
             .next_frame
             .dispatch_tree
@@ -1330,10 +1309,30 @@ impl<'a> WindowContext<'a> {
                 &mut self.window.rendered_frame.dispatch_tree,
                 self.window.focus,
             );
+        self.window.next_frame.focus = self.window.focus;
         self.window.root_view = Some(root_view);
 
-        let window = &mut self.window;
-        mem::swap(&mut window.rendered_frame, &mut window.next_frame);
+        let previous_focus_path = self.window.rendered_frame.focus_path();
+        mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
+        let current_focus_path = self.window.rendered_frame.focus_path();
+
+        if previous_focus_path != current_focus_path {
+            if !previous_focus_path.is_empty() && current_focus_path.is_empty() {
+                self.window
+                    .blur_listeners
+                    .clone()
+                    .retain(&(), |listener| listener(self));
+            }
+
+            let event = FocusEvent {
+                previous_focus_path,
+                current_focus_path,
+            };
+            self.window
+                .focus_listeners
+                .clone()
+                .retain(&(), |listener| listener(&event, self));
+        }
 
         let scene = self.window.rendered_frame.scene_builder.build();
 
@@ -1741,22 +1740,6 @@ impl<'a> WindowContext<'a> {
         result
     }
 
-    /// Register a focus listener for the next frame only. It will be cleared
-    /// on the next frame render. You should use this method only from within elements,
-    /// and we may want to enforce that better via a different context type.
-    // todo!() Move this to `FrameContext` to emphasize its individuality?
-    pub fn on_focus_changed(
-        &mut self,
-        listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static,
-    ) {
-        self.window
-            .next_frame
-            .focus_listeners
-            .push(Box::new(move |event, cx| {
-                listener(event, cx);
-            }));
-    }
-
     /// Set an input handler, such as [ElementInputHandler], which interfaces with the
     /// platform to receive textual input with proper integration with concerns such
     /// as IME interactions.
@@ -2433,7 +2416,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             (),
             Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
-                    if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) {
+                    if event.previous_focus_path.last() != Some(&focus_id)
+                        && event.current_focus_path.last() == Some(&focus_id)
+                    {
                         listener(view, cx)
                     }
                 })
@@ -2458,10 +2443,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             (),
             Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
-                    if event
-                        .focused
-                        .as_ref()
-                        .map_or(false, |focused| focus_id.contains(focused.id, cx))
+                    if !event.previous_focus_path.contains(&focus_id)
+                        && event.current_focus_path.contains(&focus_id)
                     {
                         listener(view, cx)
                     }
@@ -2487,7 +2470,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             (),
             Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
-                    if event.blurred.as_ref().map(|blurred| blurred.id) == Some(focus_id) {
+                    if event.previous_focus_path.last() == Some(&focus_id)
+                        && event.current_focus_path.last() != Some(&focus_id)
+                    {
                         listener(view, cx)
                     }
                 })
@@ -2528,10 +2513,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             (),
             Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
-                    if event
-                        .blurred
-                        .as_ref()
-                        .map_or(false, |blurred| focus_id.contains(blurred.id, cx))
+                    if event.previous_focus_path.contains(&focus_id)
+                        && !event.current_focus_path.contains(&focus_id)
                     {
                         listener(view, cx)
                     }

crates/project_panel2/src/project_panel.rs 🔗

@@ -1922,7 +1922,12 @@ mod tests {
         let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let cx = &mut VisualTestContext::from_window(*workspace, cx);
         let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
+            .update(cx, |workspace, cx| {
+                let panel = ProjectPanel::new(workspace, cx);
+                workspace.add_panel(panel.clone(), cx);
+                workspace.toggle_dock(panel.read(cx).position(cx), cx);
+                panel
+            })
             .unwrap();
 
         select_path(&panel, "root1", cx);
@@ -2275,7 +2280,12 @@ mod tests {
         let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let cx = &mut VisualTestContext::from_window(*workspace, cx);
         let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
+            .update(cx, |workspace, cx| {
+                let panel = ProjectPanel::new(workspace, cx);
+                workspace.add_panel(panel.clone(), cx);
+                workspace.toggle_dock(panel.read(cx).position(cx), cx);
+                panel
+            })
             .unwrap();
 
         select_path(&panel, "root1", cx);
@@ -2546,7 +2556,12 @@ mod tests {
         let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let cx = &mut VisualTestContext::from_window(*workspace, cx);
         let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
+            .update(cx, |workspace, cx| {
+                let panel = ProjectPanel::new(workspace, cx);
+                workspace.add_panel(panel.clone(), cx);
+                workspace.toggle_dock(panel.read(cx).position(cx), cx);
+                panel
+            })
             .unwrap();
 
         select_path(&panel, "src/", cx);

crates/search2/src/project_search.rs 🔗

@@ -277,13 +277,15 @@ pub enum ViewEvent {
 impl EventEmitter<ViewEvent> for ProjectSearchView {}
 
 impl Render for ProjectSearchView {
-    type Element = Div;
+    type Element = AnyElement;
+
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         if self.has_matches() {
             div()
                 .flex_1()
                 .size_full()
                 .child(self.results_editor.clone())
+                .into_any()
         } else {
             let model = self.model.read(cx);
             let has_no_results = model.no_results.unwrap_or(false);
@@ -356,21 +358,31 @@ impl Render for ProjectSearchView {
                     .max_w_96()
                     .child(Label::new(text).size(LabelSize::Small))
             });
-            v_stack().flex_1().size_full().justify_center().child(
-                h_stack()
-                    .size_full()
-                    .justify_center()
-                    .child(h_stack().flex_1())
-                    .child(v_stack().child(major_text).children(minor_text))
-                    .child(h_stack().flex_1()),
-            )
+            v_stack()
+                .track_focus(&self.query_editor.focus_handle(cx))
+                .flex_1()
+                .size_full()
+                .justify_center()
+                .child(
+                    h_stack()
+                        .size_full()
+                        .justify_center()
+                        .child(h_stack().flex_1())
+                        .child(v_stack().child(major_text).children(minor_text))
+                        .child(h_stack().flex_1()),
+                )
+                .into_any()
         }
     }
 }
 
 impl FocusableView for ProjectSearchView {
     fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.results_editor.focus_handle(cx)
+        if self.has_matches() {
+            self.results_editor.focus_handle(cx)
+        } else {
+            self.query_editor.focus_handle(cx)
+        }
     }
 }
 

crates/workspace2/src/item.rs 🔗

@@ -760,8 +760,9 @@ pub mod test {
     use super::{Item, ItemEvent};
     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
     use gpui::{
-        AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
-        IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+        AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, Focusable,
+        FocusableView, InteractiveElement, IntoElement, Model, Render, SharedString, Task, View,
+        ViewContext, VisualContext, WeakView,
     };
     use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
     use std::{any::Any, cell::Cell, path::Path};
@@ -909,10 +910,10 @@ pub mod test {
     }
 
     impl Render for TestItem {
-        type Element = Div;
+        type Element = Focusable<Div>;
 
         fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
-            gpui::div()
+            gpui::div().track_focus(&self.focus_handle)
         }
     }
 

crates/workspace2/src/workspace2.rs 🔗

@@ -526,6 +526,7 @@ impl Workspace {
             cx.notify()
         })
         .detach();
+
         cx.on_blur_window(|this, cx| {
             let focus_handle = this.focus_handle(cx);
             cx.focus(&focus_handle);