When opening items via project panel, only focus them on double-click

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/collab/src/rpc.rs                  | 22 +++++-----
crates/file_finder/src/file_finder.rs     |  2 
crates/project_panel/src/project_panel.rs | 51 +++++++++++++++++-------
crates/vim/src/vim_test_context.rs        |  2 
crates/workspace/src/pane.rs              | 47 +++++++++++++++-------
crates/workspace/src/workspace.rs         | 14 +++--
crates/zed/src/zed.rs                     | 21 +++++----
7 files changed, 98 insertions(+), 61 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -3898,7 +3898,7 @@ mod tests {
         let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(&params, cx));
         let editor_b = workspace_b
             .update(cx_b, |workspace, cx| {
-                workspace.open_path((worktree_id, "main.rs"), cx)
+                workspace.open_path((worktree_id, "main.rs"), true, cx)
             })
             .await
             .unwrap()
@@ -4146,7 +4146,7 @@ mod tests {
         let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(&params, cx));
         let editor_b = workspace_b
             .update(cx_b, |workspace, cx| {
-                workspace.open_path((worktree_id, "one.rs"), cx)
+                workspace.open_path((worktree_id, "one.rs"), true, cx)
             })
             .await
             .unwrap()
@@ -4898,7 +4898,7 @@ mod tests {
         let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
         let editor_a1 = workspace_a
             .update(cx_a, |workspace, cx| {
-                workspace.open_path((worktree_id, "1.txt"), cx)
+                workspace.open_path((worktree_id, "1.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -4906,7 +4906,7 @@ mod tests {
             .unwrap();
         let editor_a2 = workspace_a
             .update(cx_a, |workspace, cx| {
-                workspace.open_path((worktree_id, "2.txt"), cx)
+                workspace.open_path((worktree_id, "2.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -4917,7 +4917,7 @@ mod tests {
         let workspace_b = client_b.build_workspace(&project_b, cx_b);
         let editor_b1 = workspace_b
             .update(cx_b, |workspace, cx| {
-                workspace.open_path((worktree_id, "1.txt"), cx)
+                workspace.open_path((worktree_id, "1.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -5110,7 +5110,7 @@ mod tests {
         let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
         let _editor_a1 = workspace_a
             .update(cx_a, |workspace, cx| {
-                workspace.open_path((worktree_id, "1.txt"), cx)
+                workspace.open_path((worktree_id, "1.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -5122,7 +5122,7 @@ mod tests {
         let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
         let _editor_b1 = workspace_b
             .update(cx_b, |workspace, cx| {
-                workspace.open_path((worktree_id, "2.txt"), cx)
+                workspace.open_path((worktree_id, "2.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -5157,7 +5157,7 @@ mod tests {
             .update(cx_a, |workspace, cx| {
                 workspace.activate_next_pane(cx);
                 assert_eq!(*workspace.active_pane(), pane_a1);
-                workspace.open_path((worktree_id, "3.txt"), cx)
+                workspace.open_path((worktree_id, "3.txt"), true, cx)
             })
             .await
             .unwrap();
@@ -5165,7 +5165,7 @@ mod tests {
             .update(cx_b, |workspace, cx| {
                 workspace.activate_next_pane(cx);
                 assert_eq!(*workspace.active_pane(), pane_b1);
-                workspace.open_path((worktree_id, "4.txt"), cx)
+                workspace.open_path((worktree_id, "4.txt"), true, cx)
             })
             .await
             .unwrap();
@@ -5254,7 +5254,7 @@ mod tests {
         let workspace_a = client_a.build_workspace(&project_a, cx_a);
         let _editor_a1 = workspace_a
             .update(cx_a, |workspace, cx| {
-                workspace.open_path((worktree_id, "1.txt"), cx)
+                workspace.open_path((worktree_id, "1.txt"), true, cx)
             })
             .await
             .unwrap()
@@ -5367,7 +5367,7 @@ mod tests {
         // When client B activates a different item in the original pane, it automatically stops following client A.
         workspace_b
             .update(cx_b, |workspace, cx| {
-                workspace.open_path((worktree_id, "2.txt"), cx)
+                workspace.open_path((worktree_id, "2.txt"), true, cx)
             })
             .await
             .unwrap();

crates/file_finder/src/file_finder.rs 🔗

@@ -102,7 +102,7 @@ impl FileFinder {
         match event {
             Event::Selected(project_path) => {
                 workspace
-                    .open_path(project_path.clone(), cx)
+                    .open_path(project_path.clone(), true, cx)
                     .detach_and_log_err(cx);
                 workspace.dismiss_modal(cx);
             }

crates/project_panel/src/project_panel.rs 🔗

@@ -103,7 +103,10 @@ pub fn init(cx: &mut MutableAppContext) {
 }
 
 pub enum Event {
-    OpenedEntry(ProjectEntryId),
+    OpenedEntry {
+        entry_id: ProjectEntryId,
+        focus_opened_item: bool,
+    },
 }
 
 impl ProjectPanel {
@@ -157,19 +160,29 @@ impl ProjectPanel {
             this.update_visible_entries(None, cx);
             this
         });
-        cx.subscribe(&project_panel, move |workspace, _, event, cx| match event {
-            &Event::OpenedEntry(entry_id) => {
-                if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
-                    if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
-                        workspace
-                            .open_path(
-                                ProjectPath {
-                                    worktree_id: worktree.read(cx).id(),
-                                    path: entry.path.clone(),
-                                },
-                                cx,
-                            )
-                            .detach_and_log_err(cx);
+        cx.subscribe(&project_panel, {
+            let project_panel = project_panel.clone();
+            move |workspace, _, event, cx| match event {
+                &Event::OpenedEntry {
+                    entry_id,
+                    focus_opened_item,
+                } => {
+                    if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
+                        if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
+                            workspace
+                                .open_path(
+                                    ProjectPath {
+                                        worktree_id: worktree.read(cx).id(),
+                                        path: entry.path.clone(),
+                                    },
+                                    focus_opened_item,
+                                    cx,
+                                )
+                                .detach_and_log_err(cx);
+                            if !focus_opened_item {
+                                cx.focus(&project_panel);
+                            }
+                        }
                     }
                 }
             }
@@ -198,7 +211,10 @@ impl ProjectPanel {
                     }
                 }
             } else {
-                let event = Event::OpenedEntry(entry.id);
+                let event = Event::OpenedEntry {
+                    entry_id: entry.id,
+                    focus_opened_item: true,
+                };
                 cx.emit(event);
             }
         }
@@ -342,7 +358,10 @@ impl ProjectPanel {
     }
 
     fn open_entry(&mut self, action: &Open, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::OpenedEntry(action.entry_id));
+        cx.emit(Event::OpenedEntry {
+            entry_id: action.entry_id,
+            focus_opened_item: action.change_focus,
+        });
     }
 
     fn add_file(&mut self, _: &AddFile, cx: &mut ViewContext<Self>) {

crates/vim/src/vim_test_context.rs 🔗

@@ -50,7 +50,7 @@ impl<'a> VimTestContext<'a> {
 
         let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
         let item = workspace
-            .update(cx, |workspace, cx| workspace.open_path(file, cx))
+            .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
             .await
             .expect("Could not open test file");
 

crates/workspace/src/pane.rs 🔗

@@ -59,7 +59,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-        pane.activate_item(action.0, true, cx);
+        pane.activate_item(action.0, true, true, cx);
     });
     cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
         pane.activate_prev_item(cx);
@@ -213,7 +213,7 @@ impl Pane {
                 {
                     let prev_active_item_index = pane.active_item_index;
                     pane.nav_history.borrow_mut().set_mode(mode);
-                    pane.activate_item(index, true, cx);
+                    pane.activate_item(index, true, true, cx);
                     pane.nav_history
                         .borrow_mut()
                         .set_mode(NavigationMode::Normal);
@@ -257,6 +257,7 @@ impl Pane {
                                 workspace,
                                 pane.clone(),
                                 project_entry_id,
+                                true,
                                 cx,
                                 build_item,
                             )
@@ -287,6 +288,7 @@ impl Pane {
         workspace: &mut Workspace,
         pane: ViewHandle<Pane>,
         project_entry_id: ProjectEntryId,
+        focus_item: bool,
         cx: &mut ViewContext<Workspace>,
         build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
     ) -> Box<dyn ItemHandle> {
@@ -294,7 +296,7 @@ impl Pane {
             for (ix, item) in pane.items.iter().enumerate() {
                 if item.project_entry_id(cx) == Some(project_entry_id) {
                     let item = item.boxed_clone();
-                    pane.activate_item(ix, true, cx);
+                    pane.activate_item(ix, true, focus_item, cx);
                     return Some(item);
                 }
             }
@@ -304,7 +306,7 @@ impl Pane {
             existing_item
         } else {
             let item = build_item(cx);
-            Self::add_item(workspace, pane, item.boxed_clone(), true, cx);
+            Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx);
             item
         }
     }
@@ -313,12 +315,15 @@ impl Pane {
         workspace: &mut Workspace,
         pane: ViewHandle<Pane>,
         item: Box<dyn ItemHandle>,
-        local: bool,
+        activate_pane: bool,
+        focus_item: bool,
         cx: &mut ViewContext<Workspace>,
     ) {
         // Prevent adding the same item to the pane more than once.
         if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) {
-            pane.update(cx, |pane, cx| pane.activate_item(item_ix, local, cx));
+            pane.update(cx, |pane, cx| {
+                pane.activate_item(item_ix, activate_pane, focus_item, cx)
+            });
             return;
         }
 
@@ -327,7 +332,7 @@ impl Pane {
         pane.update(cx, |pane, cx| {
             let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
             pane.items.insert(item_idx, item);
-            pane.activate_item(item_idx, local, cx);
+            pane.activate_item(item_idx, activate_pane, focus_item, cx);
             cx.notify();
         });
     }
@@ -378,7 +383,13 @@ impl Pane {
         self.items.iter().position(|i| i.id() == item.id())
     }
 
-    pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
+    pub fn activate_item(
+        &mut self,
+        index: usize,
+        activate_pane: bool,
+        focus_item: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
         use NavigationMode::{GoingBack, GoingForward};
         if index < self.items.len() {
             let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
@@ -387,11 +398,15 @@ impl Pane {
                     && prev_active_item_ix < self.items.len())
             {
                 self.items[prev_active_item_ix].deactivated(cx);
-                cx.emit(Event::ActivateItem { local });
+                cx.emit(Event::ActivateItem {
+                    local: activate_pane,
+                });
             }
             self.update_toolbar(cx);
-            if local {
+            if focus_item {
                 self.focus_active_item(cx);
+            }
+            if activate_pane {
                 self.activate(cx);
             }
             self.autoscroll = true;
@@ -406,7 +421,7 @@ impl Pane {
         } else if self.items.len() > 0 {
             index = self.items.len() - 1;
         }
-        self.activate_item(index, true, cx);
+        self.activate_item(index, true, true, cx);
     }
 
     pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
@@ -416,7 +431,7 @@ impl Pane {
         } else {
             index = 0;
         }
-        self.activate_item(index, true, cx);
+        self.activate_item(index, true, true, cx);
     }
 
     fn close_active_item(
@@ -498,7 +513,7 @@ impl Pane {
                 if is_last_item_for_entry {
                     if cx.read(|cx| item.has_conflict(cx) && item.can_save(cx)) {
                         let mut answer = pane.update(&mut cx, |pane, cx| {
-                            pane.activate_item(item_to_close_ix, true, cx);
+                            pane.activate_item(item_to_close_ix, true, true, cx);
                             cx.prompt(
                                 PromptLevel::Warning,
                                 CONFLICT_MESSAGE,
@@ -518,7 +533,7 @@ impl Pane {
                     } else if cx.read(|cx| item.is_dirty(cx)) {
                         if cx.read(|cx| item.can_save(cx)) {
                             let mut answer = pane.update(&mut cx, |pane, cx| {
-                                pane.activate_item(item_to_close_ix, true, cx);
+                                pane.activate_item(item_to_close_ix, true, true, cx);
                                 cx.prompt(
                                     PromptLevel::Warning,
                                     DIRTY_MESSAGE,
@@ -535,7 +550,7 @@ impl Pane {
                             }
                         } else if cx.read(|cx| item.can_save_as(cx)) {
                             let mut answer = pane.update(&mut cx, |pane, cx| {
-                                pane.activate_item(item_to_close_ix, true, cx);
+                                pane.activate_item(item_to_close_ix, true, true, cx);
                                 cx.prompt(
                                     PromptLevel::Warning,
                                     DIRTY_MESSAGE,
@@ -949,7 +964,7 @@ mod tests {
 
         let close_items = workspace.update(cx, |workspace, cx| {
             pane.update(cx, |pane, cx| {
-                pane.activate_item(1, true, cx);
+                pane.activate_item(1, true, true, cx);
                 assert_eq!(pane.active_item().unwrap().id(), item2.id());
             });
 

crates/workspace/src/workspace.rs 🔗

@@ -493,7 +493,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
             if T::should_activate_item_on_event(event) {
                 pane.update(cx, |pane, cx| {
                     if let Some(ix) = pane.index_for_item(&item) {
-                        pane.activate_item(ix, true, cx);
+                        pane.activate_item(ix, true, true, cx);
                         pane.activate(cx);
                     }
                 });
@@ -898,7 +898,7 @@ impl Workspace {
                             if fs.is_file(&abs_path).await {
                                 Some(
                                     this.update(&mut cx, |this, cx| {
-                                        this.open_path(project_path, cx)
+                                        this.open_path(project_path, true, cx)
                                     })
                                     .await,
                                 )
@@ -1099,12 +1099,13 @@ impl Workspace {
 
     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
         let pane = self.active_pane().clone();
-        Pane::add_item(self, pane, item, true, cx);
+        Pane::add_item(self, pane, item, true, true, cx);
     }
 
     pub fn open_path(
         &mut self,
         path: impl Into<ProjectPath>,
+        focus_item: bool,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
         let pane = self.active_pane().downgrade();
@@ -1119,6 +1120,7 @@ impl Workspace {
                     this,
                     pane,
                     project_entry_id,
+                    focus_item,
                     cx,
                     build_item,
                 ))
@@ -1187,7 +1189,7 @@ impl Workspace {
         });
         if let Some((pane, ix)) = result {
             self.activate_pane(pane.clone(), cx);
-            pane.update(cx, |pane, cx| pane.activate_item(ix, true, cx));
+            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
             true
         } else {
             false
@@ -1277,7 +1279,7 @@ impl Workspace {
         self.activate_pane(new_pane.clone(), cx);
         if let Some(item) = pane.read(cx).active_item() {
             if let Some(clone) = item.clone_on_split(cx.as_mut()) {
-                Pane::add_item(self, new_pane.clone(), clone, true, cx);
+                Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
             }
         }
         self.center.split(&pane, &new_pane, direction).unwrap();
@@ -1961,7 +1963,7 @@ impl Workspace {
         }
 
         for (pane, item) in items_to_add {
-            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, cx);
+            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
             if pane == self.active_pane {
                 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
             }

crates/zed/src/zed.rs 🔗

@@ -446,7 +446,7 @@ mod tests {
 
         // Open the first entry
         let entry_1 = workspace
-            .update(cx, |w, cx| w.open_path(file1.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
             .await
             .unwrap();
         cx.read(|cx| {
@@ -460,7 +460,7 @@ mod tests {
 
         // Open the second entry
         workspace
-            .update(cx, |w, cx| w.open_path(file2.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
             .await
             .unwrap();
         cx.read(|cx| {
@@ -474,7 +474,7 @@ mod tests {
 
         // Open the first entry again. The existing pane item is activated.
         let entry_1b = workspace
-            .update(cx, |w, cx| w.open_path(file1.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
             .await
             .unwrap();
         assert_eq!(entry_1.id(), entry_1b.id());
@@ -492,7 +492,7 @@ mod tests {
         workspace
             .update(cx, |w, cx| {
                 w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
-                w.open_path(file2.clone(), cx)
+                w.open_path(file2.clone(), true, cx)
             })
             .await
             .unwrap();
@@ -511,8 +511,8 @@ mod tests {
         // Open the third entry twice concurrently. Only one pane item is added.
         let (t1, t2) = workspace.update(cx, |w, cx| {
             (
-                w.open_path(file3.clone(), cx),
-                w.open_path(file3.clone(), cx),
+                w.open_path(file3.clone(), true, cx),
+                w.open_path(file3.clone(), true, cx),
             )
         });
         t1.await.unwrap();
@@ -780,6 +780,7 @@ mod tests {
                         worktree_id: worktree.read(cx).id(),
                         path: Path::new("the-new-name.rs").into(),
                     },
+                    true,
                     cx,
                 )
             })
@@ -875,7 +876,7 @@ mod tests {
         let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
 
         workspace
-            .update(cx, |w, cx| w.open_path(file1.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
             .await
             .unwrap();
 
@@ -955,7 +956,7 @@ mod tests {
         let file3 = entries[2].clone();
 
         let editor1 = workspace
-            .update(cx, |w, cx| w.open_path(file1.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
             .await
             .unwrap()
             .downcast::<Editor>()
@@ -964,13 +965,13 @@ mod tests {
             editor.select_display_ranges(&[DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)], cx);
         });
         let editor2 = workspace
-            .update(cx, |w, cx| w.open_path(file2.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
             .await
             .unwrap()
             .downcast::<Editor>()
             .unwrap();
         let editor3 = workspace
-            .update(cx, |w, cx| w.open_path(file3.clone(), cx))
+            .update(cx, |w, cx| w.open_path(file3.clone(), true, cx))
             .await
             .unwrap()
             .downcast::<Editor>()