diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index fd710d596b..99fa740451 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -165,8 +165,13 @@ impl ProjectDiagnosticsEditor { } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); - workspace.open_item(diagnostics, cx); + if let Some(existing) = workspace.item_of_type::(cx) { + workspace.activate_pane_for_item(&existing, cx); + } else { + let diagnostics = + cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); + workspace.open_item(diagnostics, cx); + } } fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { @@ -191,8 +196,10 @@ impl ProjectDiagnosticsEditor { workspace.update(cx, |workspace, cx| { for (buffer, ranges) in new_selections_by_buffer { + let buffer = BufferItemHandle(buffer); + workspace.activate_pane_for_item(&buffer, cx); let editor = workspace - .open_item(BufferItemHandle(buffer), cx) + .open_item(buffer, cx) .to_any() .downcast::() .unwrap(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8f5745cc2a..b97f01ce69 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -62,6 +62,10 @@ impl ItemHandle for BufferItemHandle { Box::new(self.clone()) } + fn to_any(&self) -> gpui::AnyModelHandle { + self.0.clone().into() + } + fn downgrade(&self) -> Box { Box::new(WeakBufferItemHandle(self.0.downgrade())) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 82ec8fbcea..5b70981ba2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3097,14 +3097,39 @@ impl Drop for AnyViewHandle { pub struct AnyModelHandle { model_id: usize, + model_type: TypeId, ref_counts: Arc>, } +impl AnyModelHandle { + pub fn downcast(self) -> Option> { + if self.is::() { + let result = Some(ModelHandle { + model_id: self.model_id, + model_type: PhantomData, + ref_counts: self.ref_counts.clone(), + }); + unsafe { + Arc::decrement_strong_count(&self.ref_counts); + } + std::mem::forget(self); + result + } else { + None + } + } + + pub fn is(&self) -> bool { + self.model_type == TypeId::of::() + } +} + impl From> for AnyModelHandle { fn from(handle: ModelHandle) -> Self { handle.ref_counts.lock().inc_model(handle.model_id); Self { model_id: handle.model_id, + model_type: TypeId::of::(), ref_counts: handle.ref_counts.clone(), } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 67f92ad615..e7e1383e17 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -69,7 +69,7 @@ pub struct TabState { } pub struct Pane { - item_views: Vec>, + item_views: Vec<(usize, Box)>, active_item: usize, settings: watch::Receiver, } @@ -96,8 +96,8 @@ impl Pane { where T: 'static + ItemHandle, { - for (ix, item_view) in self.item_views.iter().enumerate() { - if item_view.item_handle(cx).id() == item_handle.id() { + for (ix, (item_id, item_view)) in self.item_views.iter().enumerate() { + if *item_id == item_handle.id() { let item_view = item_view.boxed_clone(); self.activate_item(ix, cx); return item_view; @@ -116,21 +116,33 @@ impl Pane { ) { item_view.added_to_pane(cx); let item_idx = cmp::min(self.active_item + 1, self.item_views.len()); - self.item_views.insert(item_idx, item_view); + self.item_views + .insert(item_idx, (item_view.item_handle(cx).id(), item_view)); self.activate_item(item_idx, cx); cx.notify(); } - pub fn item_views(&self) -> &[Box] { - &self.item_views + pub fn contains_item(&self, item: &dyn ItemHandle) -> bool { + let item_id = item.id(); + self.item_views + .iter() + .any(|(existing_item_id, _)| *existing_item_id == item_id) + } + + pub fn item_views(&self) -> impl Iterator> { + self.item_views.iter().map(|(_, view)| view) } pub fn active_item(&self) -> Option> { - self.item_views.get(self.active_item).cloned() + self.item_views + .get(self.active_item) + .map(|(_, view)| view.clone()) } pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option { - self.item_views.iter().position(|i| i.id() == item.id()) + self.item_views + .iter() + .position(|(_, i)| i.id() == item.id()) } pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { @@ -163,12 +175,12 @@ impl Pane { pub fn close_active_item(&mut self, cx: &mut ViewContext) { if !self.item_views.is_empty() { - self.close_item(self.item_views[self.active_item].id(), cx) + self.close_item(self.item_views[self.active_item].1.id(), cx) } } pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext) { - self.item_views.retain(|item| item.id() != item_id); + self.item_views.retain(|(_, item)| item.id() != item_id); self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); if self.item_views.is_empty() { cx.emit(Event::Remove); @@ -193,7 +205,7 @@ impl Pane { enum Tabs {} let tabs = MouseEventHandler::new::(cx.view_id(), cx, |mouse_state, cx| { let mut row = Flex::row(); - for (ix, item_view) in self.item_views.iter().enumerate() { + for (ix, (_, item_view)) in self.item_views.iter().enumerate() { let is_active = ix == self.active_item; row.add_child({ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ccb5d9799c..55538c09dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -16,9 +16,9 @@ use gpui::{ json::{self, to_string_pretty, ToJson}, keymap::Binding, platform::{CursorStyle, WindowOptions}, - AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, - PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakModelHandle, WeakViewHandle, + AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, + MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, WeakModelHandle, WeakViewHandle, }; use language::LanguageRegistry; use log::error; @@ -186,6 +186,7 @@ pub trait ItemHandle: Send + Sync { ) -> Box; fn boxed_clone(&self) -> Box; fn downgrade(&self) -> Box; + fn to_any(&self) -> AnyModelHandle; fn project_path(&self, cx: &AppContext) -> Option; } @@ -238,6 +239,10 @@ impl ItemHandle for ModelHandle { Box::new(self.downgrade()) } + fn to_any(&self) -> AnyModelHandle { + self.clone().into() + } + fn project_path(&self, cx: &AppContext) -> Option { self.read(cx).project_path() } @@ -265,6 +270,10 @@ impl ItemHandle for Box { self.as_ref().downgrade() } + fn to_any(&self) -> AnyModelHandle { + self.as_ref().to_any() + } + fn project_path(&self, cx: &AppContext) -> Option { self.as_ref().project_path(cx) } @@ -760,6 +769,12 @@ impl Workspace { .find(|i| i.project_path(cx).as_ref() == Some(path)) } + pub fn item_of_type(&self, cx: &AppContext) -> Option> { + self.items + .iter() + .find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) + } + pub fn active_item(&self, cx: &AppContext) -> Option> { self.active_pane().read(cx).active_item() } @@ -927,6 +942,26 @@ impl Workspace { pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) } + pub fn activate_pane_for_item( + &mut self, + item: &dyn ItemHandle, + cx: &mut ViewContext, + ) -> bool { + let pane = self.panes.iter().find_map(|pane| { + if pane.read(cx).contains_item(item) { + Some(pane.clone()) + } else { + None + } + }); + if let Some(pane) = pane { + self.activate_pane(pane.clone(), cx); + true + } else { + false + } + } + fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { self.active_pane = pane; self.status_bar.update(cx, |status_bar, cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d2da4fdd53..31fbc4ed78 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -273,7 +273,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.item_views().len(), 1); + assert_eq!(pane.item_views().count(), 1); }); // Open the second entry @@ -287,7 +287,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file2.clone()) ); - assert_eq!(pane.item_views().len(), 2); + assert_eq!(pane.item_views().count(), 2); }); // Open the first entry again. The existing pane item is activated. @@ -303,7 +303,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.item_views().len(), 2); + assert_eq!(pane.item_views().count(), 2); }); // Split the pane with the first entry, then open the second entry again. @@ -343,7 +343,6 @@ mod tests { ); let pane_entries = pane .item_views() - .iter() .map(|i| i.project_path(cx).unwrap()) .collect::>(); assert_eq!(pane_entries, &[file1, file2, file3]);